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

366 lines
12 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
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)
2009-11-02 20:29:30 +01:00
except:
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')
2009-07-07 19:35:44 +02:00
except:
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')
file_p = open(path, 'rb')
2009-07-07 19:35:44 +02:00
data = file_p.read()
file_p.close()
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!')
file_p = open(path, 'wb')
2009-07-07 19:35:44 +02:00
file_p.write(data)
file_p.close()
@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)
except:
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')
for line in open(sql_file).read().split(';'):
2009-07-07 19:35:44 +02:00
if (len(line)>0) and (not line.isspace()):
cursor.execute(line)
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)
info = safe_eval(open(tryton_file).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)
except:
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'"
")")
except:
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)