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

365 lines
13 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.
2011-01-08 20:29:00 +01:00
from __future__ import with_statement
2009-07-07 19:35:44 +02:00
from trytond.backend.database import DatabaseInterface, CursorInterface
from trytond.config import CONFIG
from trytond.session import Session
import os
import re
from decimal import Decimal
import datetime
import time
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
_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
2009-09-07 10:08:17 +02:00
QUOTE_SEPARATION = re.compile(r"(.*?)('.*?')", re.DOTALL)
2009-11-02 20:29:30 +01:00
EXTRACT_PATTERN = re.compile(r'EXTRACT\s*\(\s*(\S*)\s+FROM', re.I)
def extract(lookup_type, date):
if date is None:
return None
try:
if len(date) == 10:
year, month, day = map(int, date.split('-'))
date = datetime.date(year, month, day)
else:
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])
date = datetime.datetime(year, month, day, hours, minutes, seconds,
microseconds)
2010-07-08 19:53:39 +02:00
except Exception:
2009-11-02 20:29:30 +01:00
return None
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
2009-11-02 20:29:30 +01:00
elif lookup_type.lower() == 'dow':
return (date.weekday() + 1) % 7
elif lookup_type.lower() == 'doy':
return date.timetuple().tm_yday
2009-11-02 20:29:30 +01:00
elif lookup_type.lower() == 'epoch':
return int(time.mktime(date.timetuple()))
2009-11-02 20:29:30 +01:00
elif lookup_type.lower() == 'microseconds':
return date.microsecond
2009-11-02 20:29:30 +01:00
elif lookup_type.lower() == 'millennium':
return date.year / 1000 + (date.year % 1000 and 1 or 0)
elif lookup_type.lower() == 'milliseconds':
2010-02-10 18:43:39 +01:00
return date.microsecond / 1000
2009-11-02 20:29:30 +01:00
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
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
2009-09-03 01:13:50 +02:00
def split_part(text, delimiter, count):
return (text.split(delimiter) + [''] * (count - 1))[count - 1]
def replace(text, pattern, replacement):
return str(text).replace(pattern, replacement)
2009-07-07 19:35:44 +02:00
class Database(DatabaseInterface):
_memory_database = None
_conn = None
def __new__(cls, database_name=':memory:'):
if database_name == ':memory:' \
2009-07-07 19:35:44 +02:00
and cls._memory_database:
return cls._memory_database
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:':
2009-07-07 19:35:44 +02:00
Database._memory_database = self
def connect(self):
if self.database_name == ':memory:':
2009-07-07 19:35:44 +02:00
path = ':memory:'
else:
path = os.path.join(CONFIG['data_path'],
self.database_name + '.sqlite')
if not os.path.isfile(path):
raise Exception('Database doesn\'t exist!')
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)
2009-11-02 20:29:30 +01:00
self._conn.create_function('extract', 2, 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)
if sqlite.sqlite_version_info < (3, 3, 14):
self._conn.create_function('replace', 3, replace)
2009-07-07 19:35:44 +02:00
return self
def cursor(self, autocommit=False):
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
def create(self, 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['data_path'],
database_name + '.sqlite')
conn = sqlite.connect(path)
cursor = conn.cursor()
cursor.close()
conn.close()
def drop(self, cursor, database_name):
2010-12-02 00:26:33 +01:00
if database_name == ':memory:':
2009-07-07 19:35:44 +02:00
self._conn = None
return
if os.sep in database_name:
return
os.remove(os.path.join(CONFIG['data_path'],
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['data_path'],
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['data_path'],
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['data_path'])
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):
2009-09-28 11:21:11 +02:00
from trytond.tools import safe_eval
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(';'):
if (len(line)>0) and (not line.isspace()):
cursor.execute(line)
2009-07-07 19:35:44 +02:00
for i in ('ir', 'workflow', 'res', 'webdav'):
root_path = os.path.join(os.path.dirname(__file__), '..', '..')
tryton_file = os.path.join(root_path, i, '__tryton__.py')
mod_path = os.path.join(root_path, i)
2011-01-08 20:29:00 +01:00
with open(tryton_file) as fp:
info = safe_eval(fp.read())
2009-07-07 19:35:44 +02:00
active = info.get('active', False)
if active:
state = 'to install'
else:
state = 'uninstalled'
cursor.execute('INSERT INTO ir_module_module ' \
2009-07-08 14:10:07 +02:00
'(create_uid, create_date, author, website, name, ' \
'shortdesc, description, state) ' \
'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
(0, datetime.datetime.now(), info.get('author', ''),
2009-07-07 19:35:44 +02:00
info.get('website', ''), i, info.get('name', False),
info.get('description', ''), state))
cursor.execute('SELECT last_insert_rowid()')
module_id = cursor.fetchone()[0]
dependencies = info.get('depends', [])
for dependency in dependencies:
cursor.execute('INSERT INTO ir_module_module_dependency ' \
2009-07-08 14:10:07 +02:00
'(create_uid, create_date, module, name) ' \
'VALUES (%s, %s, %s, %s) ',
(0, datetime.datetime.now(), module_id, dependency))
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
self.dbname = self.database_name #XXX to remove
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):
2009-11-02 20:29:30 +01:00
buf = ""
2010-02-18 18:50:35 +01:00
for nquote, quote in QUOTE_SEPARATION.findall(sql+"''"):
nquote = nquote.replace('?', '??')
nquote = nquote.replace('%s', '?')
nquote = nquote.replace('ilike', 'like')
nquote = re.sub(EXTRACT_PATTERN, r'EXTRACT("\1",', nquote)
buf += nquote + quote
2009-11-02 20:29:30 +01:00
sql = buf[:-2]
2009-07-07 19:35:44 +02:00
try:
if params:
2009-10-20 22:58:51 +02:00
res = self.cursor.execute(sql, [isinstance(x, str) and \
unicode(x, 'utf-8') or x for x in params])
2009-07-07 19:35:44 +02:00
else:
res = self.cursor.execute(sql)
2010-07-08 19:53:39 +02:00
except Exception:
2009-07-07 19:35:44 +02:00
raise
return res
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):
try:
self.cursor.execute("SELECT name " \
"FROM sqlite_master " \
"WHERE type = 'table' AND name in (" \
"'ir_model', "
"'ir_model_field', "
"'ir_ui_view', "
"'ir_ui_menu', "
"'res_user', "
"'res_group', "
"'wkf', "
"'wkf_activity', "
"'wkf_transition', "
"'wkf_instance', "
"'wkf_workitem', "
"'wkf_witm_trans', "
"'ir_module_module', "
"'ir_module_module_dependency', "
"'ir_translation', "
"'ir_lang'"
")")
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
2009-09-07 15:18:17 +02:00
def limit_clause(self, select, limit=None, offset=None):
if limit is not None:
select += ' LIMIT %d' % limit
if offset is not None:
if limit is None:
select += ' LIMIT -1'
select += ' OFFSET %d' % offset
return select
sqlite.register_converter('NUMERIC', lambda val: Decimal(val))
sqlite.register_adapter(Decimal, lambda val: buffer(str(val)))
2010-02-18 18:50:35 +01:00
sqlite.register_adapter(Session, int)
def adapt_datetime(val):
return val.replace(tzinfo=None).isoformat(" ")
sqlite.register_adapter(datetime.datetime, adapt_datetime)