Files
tryton/trytond/backend/sqlite/database.py
T

417 lines
14 KiB
Python
Raw Normal View History

2009-07-07 19:35:44 +02:00
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.backend.database import DatabaseInterface, CursorInterface
from trytond.config import config
2009-07-07 19:35:44 +02:00
import os
from decimal import Decimal
import datetime
import time
import sys
import threading
2014-02-14 00:00:09 +01:00
import math
2009-07-07 19:35:44 +02:00
2009-07-08 23:07:48 +02:00
_FIX_ROWCOUNT = False
2009-07-07 19:35:44 +02:00
try:
from pysqlite2 import dbapi2 as sqlite
from pysqlite2.dbapi2 import IntegrityError as DatabaseIntegrityError
from pysqlite2.dbapi2 import OperationalError as DatabaseOperationalError
2009-07-08 23:07:48 +02:00
#pysqlite2 < 2.5 doesn't return correct rowcount
2012-04-01 23:08:15 +02:00
_FIX_ROWCOUNT = sqlite.version_info < (2, 5, 0)
2009-07-07 19:35:44 +02:00
except ImportError:
import sqlite3 as sqlite
from sqlite3 import IntegrityError as DatabaseIntegrityError
from sqlite3 import OperationalError as DatabaseOperationalError
2013-09-06 22:32:37 +02:00
from sql import Flavor, Table
2014-05-27 22:32:26 +02:00
from sql.functions import (Function, Extract, Position, Now, Substring,
Overlay, CharLength)
2013-09-06 22:32:37 +02:00
__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError',
'Cursor']
2009-11-02 20:29:30 +01:00
2012-04-01 23:08:15 +02:00
2013-09-06 22:32:37 +02:00
class SQLiteExtract(Function):
__slots__ = ()
_function = 'EXTRACT'
@staticmethod
def extract(lookup_type, date):
if date is None:
return None
if len(date) == 10:
year, month, day = map(int, date.split('-'))
date = datetime.date(year, month, day)
else:
2013-09-06 22:32:37 +02:00
datepart, timepart = date.split(" ")
year, month, day = map(int, datepart.split("-"))
timepart_full = timepart.split(".")
hours, minutes, seconds = map(int, timepart_full[0].split(":"))
if len(timepart_full) == 2:
microseconds = int(timepart_full[1])
else:
microseconds = 0
date = datetime.datetime(year, month, day, hours, minutes, seconds,
microseconds)
if lookup_type.lower() == 'century':
return date.year / 100 + (date.year % 100 and 1 or 0)
elif lookup_type.lower() == 'decade':
return date.year / 10
elif lookup_type.lower() == 'dow':
return (date.weekday() + 1) % 7
elif lookup_type.lower() == 'doy':
return date.timetuple().tm_yday
elif lookup_type.lower() == 'epoch':
return int(time.mktime(date.timetuple()))
elif lookup_type.lower() == 'microseconds':
return date.microsecond
elif lookup_type.lower() == 'millennium':
return date.year / 1000 + (date.year % 1000 and 1 or 0)
elif lookup_type.lower() == 'milliseconds':
return date.microsecond / 1000
elif lookup_type.lower() == 'quarter':
return date.month / 4 + 1
elif lookup_type.lower() == 'week':
return date.isocalendar()[1]
return getattr(date, lookup_type.lower())
2009-07-07 19:35:44 +02:00
2012-04-01 23:08:15 +02:00
2009-07-07 19:35:44 +02:00
def date_trunc(_type, date):
if _type == 'second':
return date
try:
2010-02-10 18:43:39 +01:00
tm_tuple = time.strptime(date, '%Y-%m-%d %H:%M:%S')
2010-07-08 19:53:39 +02:00
except Exception:
2009-07-07 19:35:44 +02:00
return None
if _type == 'year':
2010-02-10 18:43:39 +01:00
return "%i-01-01 00:00:00" % tm_tuple.tm_year
2009-07-07 19:35:44 +02:00
elif _type == 'month':
2010-02-10 18:43:39 +01:00
return "%i-%02i-01 00:00:00" % (tm_tuple.tm_year, tm_tuple.tm_mon)
2009-07-07 19:35:44 +02:00
elif _type == 'day':
2010-02-10 18:43:39 +01:00
return "%i-%02i-%02i 00:00:00" % (tm_tuple.tm_year, tm_tuple.tm_mon,
tm_tuple.tm_mday)
2009-07-07 19:35:44 +02:00
2012-04-01 23:08:15 +02:00
2009-09-03 01:13:50 +02:00
def split_part(text, delimiter, count):
if text is None:
return None
2009-09-03 01:13:50 +02:00
return (text.split(delimiter) + [''] * (count - 1))[count - 1]
2012-04-01 23:08:15 +02:00
2013-09-06 22:32:37 +02:00
class SQLitePosition(Function):
__slots__ = ()
_function = 'POSITION'
@staticmethod
def position(substring, string):
if string is None:
return
try:
return string.index(substring) + 1
except ValueError:
return 0
2013-04-14 23:17:55 +02:00
def replace(text, pattern, replacement):
return str(text).replace(pattern, replacement)
2009-07-07 19:35:44 +02:00
2013-09-06 22:32:37 +02:00
def now():
return datetime.datetime.now().isoformat(' ')
class SQLiteSubstring(Function):
__slots__ = ()
_function = 'SUBSTR'
class SQLiteOverlay(Function):
__slots__ = ()
_function = 'OVERLAY'
@staticmethod
def overlay(string, placing_string, from_, for_=None):
if for_ is None:
for_ = len(placing_string)
return string[:from_ - 1] + placing_string + string[from_ - 1 + for_:]
2014-05-27 22:32:26 +02:00
class SQLiteCharLength(Function):
__slots__ = ()
_function = 'LENGTH'
2014-02-14 00:00:09 +01:00
def sign(value):
return math.copysign(1, value)
2013-09-06 22:32:37 +02:00
MAPPING = {
Extract: SQLiteExtract,
Position: SQLitePosition,
Substring: SQLiteSubstring,
Overlay: SQLiteOverlay,
2014-05-27 22:32:26 +02:00
CharLength: SQLiteCharLength,
2013-09-06 22:32:37 +02:00
}
2009-07-07 19:35:44 +02:00
class Database(DatabaseInterface):
_local = threading.local()
2009-07-07 19:35:44 +02:00
_conn = None
2013-09-06 22:32:37 +02:00
flavor = Flavor(paramstyle='qmark', function_mapping=MAPPING)
2009-07-07 19:35:44 +02:00
def __new__(cls, database_name=':memory:'):
if (database_name == ':memory:'
and getattr(cls._local, 'memory_database', None)):
return cls._local.memory_database
2009-07-07 19:35:44 +02:00
return DatabaseInterface.__new__(cls, database_name=database_name)
def __init__(self, database_name=':memory:'):
super(Database, self).__init__(database_name=database_name)
if database_name == ':memory:':
Database._local.memory_database = self
2009-07-07 19:35:44 +02:00
def connect(self):
if self.database_name == ':memory:':
2009-07-07 19:35:44 +02:00
path = ':memory:'
else:
db_filename = self.database_name + '.sqlite'
path = os.path.join(config.get('database', 'path'), db_filename)
2009-07-07 19:35:44 +02:00
if not os.path.isfile(path):
raise IOError('Database "%s" doesn\'t exist!' % db_filename)
if self._conn is not None:
return self
2009-07-07 19:35:44 +02:00
self._conn = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES)
2013-09-06 22:32:37 +02:00
self._conn.create_function('extract', 2, SQLiteExtract.extract)
2009-07-07 19:35:44 +02:00
self._conn.create_function('date_trunc', 2, date_trunc)
2009-09-03 01:13:50 +02:00
self._conn.create_function('split_part', 3, split_part)
2013-09-06 22:32:37 +02:00
self._conn.create_function('position', 2, SQLitePosition.position)
self._conn.create_function('overlay', 3, SQLiteOverlay.overlay)
self._conn.create_function('overlay', 4, SQLiteOverlay.overlay)
if sqlite.sqlite_version_info < (3, 3, 14):
self._conn.create_function('replace', 3, replace)
2013-09-06 22:32:37 +02:00
self._conn.create_function('now', 0, now)
2014-02-14 00:00:09 +01:00
self._conn.create_function('sign', 1, sign)
self._conn.execute('PRAGMA foreign_keys = ON')
2009-07-07 19:35:44 +02:00
return self
2011-08-21 11:05:51 +02:00
def cursor(self, autocommit=False, readonly=False):
2009-07-07 19:35:44 +02:00
if self._conn is None:
self.connect()
if autocommit:
self._conn.isolation_level = None
else:
self._conn.isolation_level = 'IMMEDIATE'
return Cursor(self._conn, self.database_name)
def close(self):
if self.database_name == ':memory:':
2009-07-07 19:35:44 +02:00
return
if self._conn is None:
return
self._conn = None
@staticmethod
def create(cursor, database_name):
if database_name == ':memory:':
2009-07-07 19:35:44 +02:00
path = ':memory:'
else:
if os.sep in database_name:
return
path = os.path.join(config.get('database', 'path'),
2009-07-07 19:35:44 +02:00
database_name + '.sqlite')
2011-06-07 20:10:17 +02:00
with sqlite.connect(path) as conn:
cursor = conn.cursor()
cursor.close()
2009-07-07 19:35:44 +02:00
@classmethod
def drop(cls, cursor, database_name):
2010-12-02 00:26:33 +01:00
if database_name == ':memory:':
cls._local.memory_database._conn = None
2009-07-07 19:35:44 +02:00
return
if os.sep in database_name:
return
os.remove(os.path.join(config.get('database', 'path'),
2009-07-07 19:35:44 +02:00
database_name + '.sqlite'))
@staticmethod
def dump(database_name):
if database_name == ':memory:':
2009-07-07 19:35:44 +02:00
raise Exception('Unable to dump memory database!')
if os.sep in database_name:
raise Exception('Wrong database name!')
path = os.path.join(config.get('database', 'path'),
2009-07-07 19:35:44 +02:00
database_name + '.sqlite')
2011-01-08 20:29:00 +01:00
with open(path, 'rb') as file_p:
data = file_p.read()
2009-07-07 19:35:44 +02:00
return data
@staticmethod
def restore(database_name, data):
if database_name == ':memory:':
2009-07-07 19:35:44 +02:00
raise Exception('Unable to restore memory database!')
if os.sep in database_name:
raise Exception('Wrong database name!')
path = os.path.join(config.get('database', 'path'),
2009-07-07 19:35:44 +02:00
database_name + '.sqlite')
if os.path.isfile(path):
raise Exception('Database already exists!')
2011-01-08 20:29:00 +01:00
with open(path, 'wb') as file_p:
file_p.write(data)
2009-07-07 19:35:44 +02:00
@staticmethod
def list(cursor):
res = []
2010-03-04 14:02:27 +01:00
listdir = [':memory:']
try:
listdir += os.listdir(config.get('database', 'path'))
2010-03-04 14:02:27 +01:00
except OSError:
pass
for db_file in listdir:
2010-02-18 18:50:35 +01:00
if db_file.endswith('.sqlite') or db_file == ':memory:':
if db_file == ':memory:':
2009-07-07 19:35:44 +02:00
db_name = ':memory:'
else:
2010-02-18 18:50:35 +01:00
db_name = db_file[:-7]
2009-07-07 19:35:44 +02:00
try:
database = Database(db_name)
2010-07-08 19:53:39 +02:00
except Exception:
2009-07-07 19:35:44 +02:00
continue
cursor2 = database.cursor()
if cursor2.test():
res.append(db_name)
cursor2.close()
return res
@staticmethod
def init(cursor):
from trytond.modules import get_module_info
2009-07-07 19:35:44 +02:00
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
2011-01-08 20:29:00 +01:00
with open(sql_file) as fp:
for line in fp.read().split(';'):
2012-04-01 23:08:15 +02:00
if (len(line) > 0) and (not line.isspace()):
2011-01-08 20:29:00 +01:00
cursor.execute(line)
2009-07-07 19:35:44 +02:00
2013-09-06 22:32:37 +02:00
ir_module = Table('ir_module_module')
ir_module_dependency = Table('ir_module_module_dependency')
for module in ('ir', 'res', 'webdav'):
state = 'uninstalled'
if module in ('ir', 'res'):
2009-07-07 19:35:44 +02:00
state = 'to install'
info = get_module_info(module)
2013-09-06 22:32:37 +02:00
insert = ir_module.insert(
[ir_module.create_uid, ir_module.create_date, ir_module.name,
ir_module.state],
[[0, Now(), module, state]])
cursor.execute(*insert)
2009-07-07 19:35:44 +02:00
cursor.execute('SELECT last_insert_rowid()')
module_id, = cursor.fetchone()
for dependency in info.get('depends', []):
2013-09-06 22:32:37 +02:00
insert = ir_module_dependency.insert(
[ir_module_dependency.create_uid,
ir_module_dependency.create_date,
ir_module_dependency.module, ir_module_dependency.name
],
[[0, Now(), module_id, dependency]])
cursor.execute(*insert)
2009-07-07 19:35:44 +02:00
class _Cursor(sqlite.Cursor):
def __build_dict(self, row):
return dict((desc[0], row[i])
for i, desc in enumerate(self.description))
2009-07-07 19:35:44 +02:00
def dictfetchone(self):
row = self.fetchone()
if row:
return self.__build_dict(row)
else:
return row
def dictfetchmany(self, size):
rows = self.fetchmany(size)
return [self.__build_dict(row) for row in rows]
2009-07-07 19:35:44 +02:00
def dictfetchall(self):
rows = self.fetchall()
return [self.__build_dict(row) for row in rows]
2009-07-07 19:35:44 +02:00
class Cursor(CursorInterface):
2009-07-27 15:14:03 +02:00
IN_MAX = 200
2009-07-07 19:35:44 +02:00
def __init__(self, conn, database_name):
2009-08-11 11:13:14 +02:00
super(Cursor, self).__init__()
2009-07-07 19:35:44 +02:00
self._conn = conn
self.database_name = database_name
2012-04-01 23:08:15 +02:00
self.dbname = self.database_name # XXX to remove
2009-07-07 19:35:44 +02:00
self.cursor = conn.cursor(_Cursor)
def __getattr__(self, name):
2009-07-08 23:07:48 +02:00
if _FIX_ROWCOUNT and name == 'rowcount':
return -1
2009-07-07 19:35:44 +02:00
return getattr(self.cursor, name)
def execute(self, sql, params=None):
2013-09-06 22:32:37 +02:00
if params:
return self.cursor.execute(sql, params)
else:
return self.cursor.execute(sql)
2009-07-07 19:35:44 +02:00
def close(self, close=False):
self.cursor.close()
self.rollback()
def commit(self):
2009-08-11 11:13:14 +02:00
super(Cursor, self).commit()
2009-07-07 19:35:44 +02:00
self._conn.commit()
def rollback(self):
2009-08-11 11:13:14 +02:00
super(Cursor, self).rollback()
2009-07-07 19:35:44 +02:00
self._conn.rollback()
def test(self):
2013-09-06 22:32:37 +02:00
sqlite_master = Table('sqlite_master')
select = sqlite_master.select(sqlite_master.name)
select.where = sqlite_master.type == 'table'
select.where &= sqlite_master.name.in_([
'ir_model',
'ir_model_field',
'ir_ui_view',
'ir_ui_menu',
'res_user',
'res_group',
'ir_module_module',
'ir_module_module_dependency',
'ir_translation',
'ir_lang',
])
try:
2013-09-06 22:32:37 +02:00
self.cursor.execute(*select)
2010-07-08 19:53:39 +02:00
except Exception:
return False
2009-07-07 19:35:44 +02:00
return len(self.cursor.fetchall()) != 0
def lastid(self):
self.cursor.execute('SELECT last_insert_rowid()')
return self.cursor.fetchone()[0]
2010-02-25 16:43:04 +01:00
def lock(self, table):
pass
2009-07-07 19:35:44 +02:00
def has_constraint(self):
return False
sqlite.register_converter('NUMERIC', lambda val: Decimal(val))
if sys.version_info[0] == 2:
sqlite.register_adapter(Decimal, lambda val: buffer(str(val)))
else:
sqlite.register_adapter(Decimal, lambda val: bytes(str(val)))
2012-04-01 23:08:15 +02:00
def adapt_datetime(val):
return val.replace(tzinfo=None).isoformat(" ")
sqlite.register_adapter(datetime.datetime, adapt_datetime)
2012-02-07 16:11:22 +01:00
sqlite.register_adapter(datetime.time, lambda val: val.isoformat())
sqlite.register_converter('TIME', lambda val: datetime.time(*map(int,
val.split(':'))))