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

429 lines
14 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +01: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
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
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
2014-12-02 12:54:01 +01: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
2015-07-01 19:41:49 +02:00
from sql.functions import (Function, Extract, Position, Substring,
Overlay, CharLength, CurrentTimestamp)
2013-09-06 22:32:37 +02:00
__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
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'
2015-07-01 19:41:49 +02:00
class SQLiteCurrentTimestamp(Function):
__slots__ = ()
_function = 'NOW' # More precise
2014-02-14 00:00:09 +01:00
def sign(value):
2015-02-04 23:19:45 +01:00
if value > 0:
return 1
elif value < 0:
return -1
else:
return value
2014-02-14 00:00:09 +01:00
def greatest(*args):
args = filter(lambda a: a is not None, args)
if args:
return max(args)
else:
return None
def least(*args):
args = filter(lambda a: a is not None, args)
if args:
return min(args)
else:
return None
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,
2015-07-01 19:41:49 +02:00
CurrentTimestamp: SQLiteCurrentTimestamp,
2013-09-06 22:32:37 +02:00
}
class SQLiteCursor(sqlite.Cursor):
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
pass
class SQLiteConnection(sqlite.Connection):
def cursor(self):
return super(SQLiteConnection, self).cursor(SQLiteCursor)
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)
IN_MAX = 200
2009-07-07 19:35:44 +02:00
def __new__(cls, name=':memory:'):
if (name == ':memory:'
and getattr(cls._local, 'memory_database', None)):
return cls._local.memory_database
return DatabaseInterface.__new__(cls, name=name)
2009-07-07 19:35:44 +02:00
def __init__(self, name=':memory:'):
super(Database, self).__init__(name=name)
if name == ':memory:':
Database._local.memory_database = self
2009-07-07 19:35:44 +02:00
def connect(self):
if self.name == ':memory:':
2009-07-07 19:35:44 +02:00
path = ':memory:'
else:
db_filename = self.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
self._conn = sqlite.connect(path,
detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES,
factory=SQLiteConnection)
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.create_function('greatest', -1, greatest)
self._conn.create_function('least', -1, least)
self._conn.execute('PRAGMA foreign_keys = ON')
2009-07-07 19:35:44 +02:00
return self
def get_connection(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 self._conn
def put_connection(self, connection=None, close=False):
pass
2009-07-07 19:35:44 +02:00
def close(self):
if self.name == ':memory:':
2009-07-07 19:35:44 +02:00
return
if self._conn is None:
return
self._conn = None
@classmethod
def create(cls, connection, 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
def drop(self, connection, database_name):
2010-12-02 00:26:33 +01:00
if database_name == ':memory:':
self._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
def list(self):
2009-07-07 19:35:44 +02:00
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).connect()
2010-07-08 19:53:39 +02:00
except Exception:
2009-07-07 19:35:44 +02:00
continue
if database.test():
2009-07-07 19:35:44 +02:00
res.append(db_name)
database.close()
2009-07-07 19:35:44 +02:00
return res
def init(self):
from trytond.modules import get_module_info
with self._conn as conn:
cursor = conn.cursor()
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
with open(sql_file) as fp:
for line in fp.read().split(';'):
if (len(line) > 0) and (not line.isspace()):
cursor.execute(line)
2009-07-07 19:35:44 +02:00
ir_module = Table('ir_module')
ir_module_dependency = Table('ir_module_dependency')
for module in ('ir', 'res'):
state = 'uninstalled'
if module in ('ir', 'res'):
state = 'to install'
info = get_module_info(module)
insert = ir_module.insert(
[ir_module.create_uid, ir_module.create_date,
ir_module.name, ir_module.state],
[[0, CurrentTimestamp(), module, state]])
2013-09-06 22:32:37 +02:00
cursor.execute(*insert)
cursor.execute('SELECT last_insert_rowid()')
module_id, = cursor.fetchone()
for dependency in info.get('depends', []):
insert = ir_module_dependency.insert(
[ir_module_dependency.create_uid,
ir_module_dependency.create_date,
ir_module_dependency.module,
ir_module_dependency.name,
],
[[0, CurrentTimestamp(), module_id, dependency]])
cursor.execute(*insert)
conn.commit()
2009-07-07 19:35:44 +02:00
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',
2015-05-28 21:02:01 +02:00
'ir_module',
'ir_module_dependency',
2013-09-06 22:32:37 +02:00
'ir_translation',
'ir_lang',
])
with self._conn as conn:
cursor = conn.cursor()
try:
cursor.execute(*select)
except Exception:
return False
return len(cursor.fetchall()) != 0
2009-07-07 19:35:44 +02:00
def lastid(self, cursor):
# This call is not thread safe
return cursor.lastrowid
2009-07-07 19:35:44 +02:00
def lock(self, connection, table):
2010-02-25 16:43:04 +01:00
pass
2009-07-07 19:35:44 +02:00
def has_constraint(self):
return False
2015-07-28 08:04:29 +02:00
def has_multirow_insert(self):
return True
sqlite.register_converter('NUMERIC', lambda val: Decimal(val.decode('utf-8')))
if sys.version_info[0] == 2:
2015-03-16 20:13:03 +01:00
sqlite.register_adapter(Decimal, lambda val: buffer(str(val)))
sqlite.register_adapter(bytearray, lambda val: buffer(val))
else:
sqlite.register_adapter(Decimal, lambda val: str(val).encode('utf-8'))
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.decode('utf-8').split(':'))))
2015-02-09 22:58:56 +01:00
sqlite.register_adapter(datetime.timedelta, lambda val: val.total_seconds())
def convert_interval(value):
value = float(value)
# It is not allowed to instatiate timedelta with the min/max total seconds
if value >= _interval_max:
return datetime.timedelta.max
elif value <= _interval_min:
return datetime.timedelta.min
return datetime.timedelta(seconds=value)
_interval_max = datetime.timedelta.max.total_seconds()
_interval_min = datetime.timedelta.min.total_seconds()
sqlite.register_converter('INTERVAL', convert_interval)