Add sqlite backend
This commit is contained in:
1 parent
af9ad2664b
commit
23c5eff184
9 files changed
+956
-3
No files matched your search
+5
-1
@@ -21,6 +21,10 @@
|
||||
# Configure the port for the webdav protocol to listen
|
||||
#webdavport = 8080
|
||||
|
||||
# Configure the database type
|
||||
# allowed values are postgresql, sqlite
|
||||
db_type = postgresql
|
||||
|
||||
# Configure the database connection
|
||||
## Note: Only databases owned by db_user will be displayed in the connection dialog
|
||||
## of the Tryton client. db_user must have create permission for new databases
|
||||
@@ -54,7 +58,7 @@
|
||||
#smtp_password = False
|
||||
#smtp_user = False
|
||||
|
||||
# Configure the path to store attachments
|
||||
# Configure the path to store attachments and sqlite database
|
||||
#data_path = /var/lib/trytond
|
||||
|
||||
# Limit the number of threads
|
||||
|
||||
@@ -41,12 +41,12 @@ setup(name=PACKAGE,
|
||||
],
|
||||
license=LICENSE,
|
||||
install_requires=[
|
||||
'psycopg2 >= 2.0',
|
||||
'lxml',
|
||||
'egenix-mx-base',
|
||||
'relatorio >= 0.2.0',
|
||||
],
|
||||
extras_require={
|
||||
'PostgreSQL': ['psycopg2 >= 2.0'],
|
||||
'WebDAV': ['PyWebDAV >= 0.9.3'],
|
||||
'PDF': ['openoffice.interact'],
|
||||
'SSL': ['pyOpenSSL'],
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#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 postgresql import *
|
||||
from trytond.config import CONFIG
|
||||
if CONFIG['db_type'] == 'sqlite':
|
||||
from sqlite import *
|
||||
else:
|
||||
from postgresql import *
|
||||
@@ -0,0 +1,6 @@
|
||||
#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 database import *
|
||||
from fields import *
|
||||
from table import *
|
||||
@@ -0,0 +1,297 @@
|
||||
#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 logging
|
||||
import os
|
||||
import re
|
||||
import mx.DateTime
|
||||
from decimal import Decimal
|
||||
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
from pysqlite2.dbapi2 import IntegrityError as DatabaseIntegrityError
|
||||
from pysqlite2.dbapi2 import OperationalError as DatabaseOperationalError
|
||||
except ImportError:
|
||||
import sqlite3 as sqlite
|
||||
from sqlite3 import IntegrityError as DatabaseIntegrityError
|
||||
from sqlite3 import OperationalError as DatabaseOperationalError
|
||||
|
||||
def date_trunc(_type, date):
|
||||
if _type == 'second':
|
||||
return date
|
||||
try:
|
||||
date = mx.DateTime.strptime(date, '%Y-%m-%d %H:%M:%S')
|
||||
except:
|
||||
return None
|
||||
if _type == 'year':
|
||||
return "%i-01-01 00:00:00" % dt.year
|
||||
elif _type == 'month':
|
||||
return "%i-%02i-01 00:00:00" % (dt.year, dt.month)
|
||||
elif _type == 'day':
|
||||
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
||||
|
||||
def now():
|
||||
return mx.DateTime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
class Database(DatabaseInterface):
|
||||
|
||||
_memory_database = None
|
||||
_memory_created = False
|
||||
_conn = None
|
||||
|
||||
def __new__(cls, database_name=':memory:'):
|
||||
if database_name in (':memory:', 'memory') \
|
||||
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 in (':memory:', 'memory'):
|
||||
Database._memory_database = self
|
||||
|
||||
def connect(self):
|
||||
if self._conn is not None:
|
||||
return self
|
||||
if self.database_name in (':memory:', 'memory'):
|
||||
path = ':memory:'
|
||||
if self.database_name == 'memory' \
|
||||
and not self._memory_created:
|
||||
raise Exception('Database doesn\'t exist!')
|
||||
else:
|
||||
path = os.path.join(CONFIG['data_path'],
|
||||
self.database_name + '.sqlite')
|
||||
if not os.path.isfile(path):
|
||||
raise Exception('Database doesn\'t exist!')
|
||||
self._conn = sqlite.connect(path, detect_types=sqlite.PARSE_DECLTYPES)
|
||||
self._conn.create_function('date_trunc', 2, date_trunc)
|
||||
self._conn.create_function('now', 0, now)
|
||||
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 in (':memory:', 'memory'):
|
||||
return
|
||||
if self._conn is None:
|
||||
return
|
||||
self._conn = None
|
||||
|
||||
def create(self, cursor, database_name):
|
||||
if database_name in (':memory:', 'memory'):
|
||||
path = ':memory:'
|
||||
if database_name == 'memory':
|
||||
self._memory_created = True
|
||||
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):
|
||||
if database_name in (':memory', 'memory'):
|
||||
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 in (':memory:', 'memory'):
|
||||
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 = file(path)
|
||||
data = file_p.read()
|
||||
file_p.close()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def restore(database_name, data):
|
||||
if database_name in (':memory:', 'memory'):
|
||||
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 = file(path, 'wb')
|
||||
file_p.write(data)
|
||||
file_p.close()
|
||||
|
||||
@staticmethod
|
||||
def list(cursor):
|
||||
res = []
|
||||
for file in os.listdir(CONFIG['data_path']) + ['memory']:
|
||||
if file.endswith('.sqlite') or file == 'memory':
|
||||
if file == 'memory':
|
||||
db_name = ':memory:'
|
||||
else:
|
||||
db_name = file[:-7]
|
||||
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):
|
||||
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
|
||||
for line in file(sql_file).read().split(';'):
|
||||
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 = eval(file(tryton_file).read())
|
||||
active = info.get('active', False)
|
||||
if active:
|
||||
state = 'to install'
|
||||
else:
|
||||
state = 'uninstalled'
|
||||
cursor.execute('INSERT INTO ir_module_module ' \
|
||||
'(author, website, name, shortdesc, ' \
|
||||
'description, state) ' \
|
||||
'VALUES (%s, %s, %s, %s, %s, %s)',
|
||||
(info.get('author', ''),
|
||||
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 ' \
|
||||
'(module, name) VALUES (%s, %s)',
|
||||
(module_id, dependency))
|
||||
|
||||
|
||||
class _Cursor(sqlite.Cursor):
|
||||
|
||||
def __build_dict(self, row):
|
||||
res = {}
|
||||
for i in range(len(self.description)):
|
||||
res[self.description[i][0]] = row[i]
|
||||
return res
|
||||
|
||||
def dictfetchone(self):
|
||||
row = self.fetchone()
|
||||
if row:
|
||||
return self.__build_dict(row)
|
||||
else:
|
||||
return row
|
||||
|
||||
def dictfetchmany(self, size):
|
||||
res = []
|
||||
rows = self.fetchmany(size)
|
||||
for row in rows:
|
||||
res.append(self.__build_dict(row))
|
||||
return res
|
||||
|
||||
def dictfetchall(self):
|
||||
res = []
|
||||
rows = self.fetchall()
|
||||
for row in rows:
|
||||
res.append(self.__build_dict(row))
|
||||
return res
|
||||
|
||||
|
||||
class Cursor(CursorInterface):
|
||||
IN_MAX = 500
|
||||
|
||||
def __init__(self, conn, database_name):
|
||||
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):
|
||||
return getattr(self.cursor, name)
|
||||
|
||||
def execute(self, sql, params=None):
|
||||
sql = sql.replace('?', '??')
|
||||
quote_separation = re.compile(r"(.*?)('.*?')", re.DOTALL)
|
||||
notQuoted_quoted = quote_separation.findall(sql+"''")
|
||||
replaced = [nq\
|
||||
.replace('%s', '?')\
|
||||
.replace('ilike', 'like') \
|
||||
+ q for (nq, q) in notQuoted_quoted]
|
||||
sql = "".join(replaced)[:-2]
|
||||
try:
|
||||
if params:
|
||||
res = self.cursor.execute(sql, params)
|
||||
else:
|
||||
res = self.cursor.execute(sql)
|
||||
except:
|
||||
print sql, params
|
||||
raise
|
||||
return res
|
||||
|
||||
def close(self, close=False):
|
||||
self.cursor.close()
|
||||
self.rollback()
|
||||
|
||||
def commit(self):
|
||||
self._conn.commit()
|
||||
|
||||
def rollback(self):
|
||||
self._conn.rollback()
|
||||
|
||||
def test(self):
|
||||
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'"
|
||||
")")
|
||||
return len(self.cursor.fetchall()) != 0
|
||||
|
||||
def lastid(self):
|
||||
self.cursor.execute('SELECT last_insert_rowid()')
|
||||
return self.cursor.fetchone()[0]
|
||||
|
||||
def has_lock(self):
|
||||
return False
|
||||
|
||||
sqlite.register_converter('NUMERIC', lambda val: Decimal(str(val)))
|
||||
sqlite.register_adapter(Decimal, lambda val: float(val))
|
||||
sqlite.register_adapter(Session, lambda val: int(val))
|
||||
@@ -0,0 +1,196 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
try:
|
||||
import hashlib
|
||||
except ImportError:
|
||||
hashlib = None
|
||||
import sha
|
||||
|
||||
|
||||
class Field(object):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
if value is None or value == False:
|
||||
return None
|
||||
elif isinstance(value, str):
|
||||
return unicode(value, 'utf-8')
|
||||
elif isinstance(value, unicode):
|
||||
return value
|
||||
return unicode(value)
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return None
|
||||
|
||||
|
||||
class Boolean(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return value and '1' or '0'
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('BOOLEAN', 'BOOLEAN')
|
||||
|
||||
|
||||
class Integer(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return int(value or 0)
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('INTEGER', 'INTEGER')
|
||||
|
||||
|
||||
class BigInteger(Integer):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('INTEGER', 'INTEGER')
|
||||
|
||||
|
||||
class Char(Field):
|
||||
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('VARCHAR', 'VARCHAR')
|
||||
|
||||
|
||||
class Sha(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
if hashlib:
|
||||
return value and hashlib.sha1(value).hexdigest() or ''
|
||||
else:
|
||||
return value and sha.new(value).hexdigest() or ''
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('VARCHAR', 'VARCHAR(40)')
|
||||
|
||||
|
||||
class Text(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('TEXT', 'TEXT')
|
||||
|
||||
|
||||
class Float(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return float(value or 0.0)
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('FLOAT', 'FLOAT')
|
||||
|
||||
|
||||
class Numeric(Float):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('NUMERIC', 'NUMERIC')
|
||||
|
||||
|
||||
class Date(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('DATE', 'DATE')
|
||||
|
||||
|
||||
class DateTime(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('TIMESTAMP', 'TIMESTAMP')
|
||||
|
||||
|
||||
class Time(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('TIME', 'TIME')
|
||||
|
||||
|
||||
class Binary(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return value or None
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('BLOB', 'BLOB')
|
||||
|
||||
|
||||
class Selection(Char):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('VARCHAR', 'VARCHAR')
|
||||
|
||||
|
||||
class Reference(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('VARCHAR', 'VARCHAR')
|
||||
|
||||
|
||||
class Many2One(Field):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return value and int(value) or None
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('INTEGER', 'INTEGER')
|
||||
|
||||
|
||||
class One2Many(Field):
|
||||
pass
|
||||
|
||||
|
||||
class Many2Many(Field):
|
||||
pass
|
||||
|
||||
|
||||
class Function(Field):
|
||||
pass
|
||||
|
||||
|
||||
class Property(Function):
|
||||
pass
|
||||
|
||||
|
||||
FIELDS = {
|
||||
'boolean': Boolean,
|
||||
'integer': Integer,
|
||||
'biginteger': BigInteger,
|
||||
'char': Char,
|
||||
'sha': Sha,
|
||||
'text': Text,
|
||||
'float': Float,
|
||||
'numeric': Numeric,
|
||||
'date': Date,
|
||||
'datetime': DateTime,
|
||||
'time': Time,
|
||||
'binary': Binary,
|
||||
'selection': Selection,
|
||||
'reference': Reference,
|
||||
'many2one': Many2One,
|
||||
'one2many': One2Many,
|
||||
'many2many': Many2Many,
|
||||
'function': Function,
|
||||
'property': Property,
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
-------------------------------------------------------------------------
|
||||
-- Modules Description
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE ir_model (
|
||||
id INTEGER PRIMARY KEY,
|
||||
model VARCHAR,
|
||||
name VARCHAR,
|
||||
info TEXT,
|
||||
module VARCHAR
|
||||
);
|
||||
|
||||
CREATE TABLE ir_model_field (
|
||||
id INTEGER PRIMARY KEY,
|
||||
model INTEGER,
|
||||
name VARCHAR,
|
||||
relation VARCHAR,
|
||||
field_description VARCHAR,
|
||||
ttype VARCHAR,
|
||||
help TEXT,
|
||||
module VARCHAR
|
||||
);
|
||||
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- Actions
|
||||
-------------------------------------------------------------------------
|
||||
CREATE TABLE ir_ui_view (
|
||||
id INTEGER PRIMARY KEY,
|
||||
model VARCHAR,
|
||||
"type" VARCHAR,
|
||||
arch TEXT,
|
||||
field_childs VARCHAR,
|
||||
priority INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE ir_ui_menu (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_id INTEGER,
|
||||
name VARCHAR,
|
||||
icon VARCHAR
|
||||
);
|
||||
|
||||
|
||||
|
||||
--------------------------------
|
||||
-- Translation
|
||||
--------------------------------
|
||||
|
||||
CREATE TABLE ir_translation (
|
||||
id INTEGER PRIMARY KEY,
|
||||
lang VARCHAR,
|
||||
src TEXT,
|
||||
name VARCHAR,
|
||||
res_id INTEGER,
|
||||
value TEXT,
|
||||
"type" VARCHAR,
|
||||
module VARCHAR,
|
||||
fuzzy BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE ir_lang (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
code VARCHAR,
|
||||
translatable BOOLEAN,
|
||||
active BOOLEAN,
|
||||
direction VARCHAR
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Res user
|
||||
---------------------------------
|
||||
|
||||
-- level:
|
||||
-- 0 RESTRICT TO USER
|
||||
-- 1 RESTRICT TO GROUP
|
||||
-- 2 PUBLIC
|
||||
|
||||
CREATE TABLE res_user (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
active BOOLEAN,
|
||||
login VARCHAR,
|
||||
password VARCHAR(40),
|
||||
-- action_id INTEGER references ir_act_window on delete set null,
|
||||
action INTEGER
|
||||
);
|
||||
|
||||
insert into res_user (id,login,password,name,action,active) values (0,'root',NULL,'Root',NULL,0);
|
||||
|
||||
CREATE TABLE res_group (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "res_user-res_group" (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uid INTEGER,
|
||||
gid INTEGER
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Workflows
|
||||
---------------------------------
|
||||
|
||||
create table wkf
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
osv VARCHAR,
|
||||
on_create BOOLEAN
|
||||
);
|
||||
|
||||
create table wkf_activity
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
workflow INTEGER,
|
||||
subflow INTEGER,
|
||||
split_mode VARCHAR,
|
||||
join_mode VARCHAR,
|
||||
kind VARCHAR,
|
||||
name VARCHAR,
|
||||
signal_send VARCHAR,
|
||||
flow_start BOOLEAN,
|
||||
flow_stop BOOLEAN,
|
||||
action TEXT
|
||||
);
|
||||
|
||||
create table wkf_transition
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
act_from INTEGER,
|
||||
act_to INTEGER,
|
||||
condition VARCHAR,
|
||||
trigger_expr_id VARCHAR,
|
||||
signal VARCHAR,
|
||||
"group" INTEGER
|
||||
);
|
||||
|
||||
create table wkf_instance
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
workflow INTEGER,
|
||||
uid INTEGER,
|
||||
res_id INTEGER not null,
|
||||
res_type VARCHAR not null,
|
||||
state VARCHAR not null
|
||||
);
|
||||
|
||||
create table wkf_workitem
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
activity INTEGER,
|
||||
instance INTEGER,
|
||||
subflow INTEGER,
|
||||
state VARCHAR
|
||||
);
|
||||
|
||||
create table wkf_witm_trans
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
trans_id INTEGER,
|
||||
inst_id INTEGER
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Modules
|
||||
---------------------------------
|
||||
|
||||
CREATE TABLE ir_module_module (
|
||||
id INTEGER PRIMARY KEY,
|
||||
create_uid INTEGER,
|
||||
create_date TIMESTAMP,
|
||||
write_date TIMESTAMP,
|
||||
write_uid INTEGER,
|
||||
website VARCHAR,
|
||||
name VARCHAR,
|
||||
author VARCHAR,
|
||||
url VARCHAR,
|
||||
state VARCHAR,
|
||||
shortdesc VARCHAR,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE ir_module_module_dependency (
|
||||
id INTEGER PRIMARY KEY,
|
||||
create_uid INTEGER,
|
||||
create_date TIMESTAMP,
|
||||
write_date TIMESTAMP,
|
||||
write_uid INTEGER,
|
||||
name VARCHAR,
|
||||
module INTEGER
|
||||
);
|
||||
@@ -0,0 +1,251 @@
|
||||
#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.table import TableHandlerInterface
|
||||
from trytond.backend.sqlite.fields import FIELDS
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
||||
class TableHandler(TableHandlerInterface):
|
||||
def __init__(self, cursor, model, module_name=None, history=False):
|
||||
super(TableHandler, self).__init__(cursor, model,
|
||||
module_name=module_name, history=history)
|
||||
self._columns = {}
|
||||
self._constraints = []
|
||||
self._fk_deltypes = {}
|
||||
self._indexes = []
|
||||
self._field2module = {}
|
||||
self._model = model
|
||||
|
||||
# Create new table if necessary
|
||||
if not self.table_exist(self.cursor, self.table_name):
|
||||
if not self.history:
|
||||
self.cursor.execute('CREATE TABLE "%s" ' \
|
||||
'(id INTEGER PRIMARY KEY)' % \
|
||||
self.table_name)
|
||||
else:
|
||||
self.cursor.execute('CREATE TABLE "%s" ' \
|
||||
'(__id INTEGER PRIMARY KEY, ' \
|
||||
'id INTEGER)' % self.table_name)
|
||||
|
||||
self._update_definitions()
|
||||
|
||||
@staticmethod
|
||||
def table_exist(cursor, table_name):
|
||||
cursor.execute("SELECT name FROM sqlite_master " \
|
||||
"WHERE type = 'table' AND name = %s",
|
||||
(table_name,))
|
||||
return bool(cursor.fetchone())
|
||||
|
||||
@staticmethod
|
||||
def table_rename(cursor, old_name, new_name):
|
||||
if TableHandler.table_exist(cursor, old_name) and \
|
||||
not TableHandler.table_exist(cursor, new_name):
|
||||
cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' % \
|
||||
(old_name, new_name))
|
||||
|
||||
@staticmethod
|
||||
def sequence_exist(cursor, sequence_name):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def sequence_rename(cursor, old_name, new_name):
|
||||
pass
|
||||
|
||||
def column_exist(self, column_name):
|
||||
return column_name in self._columns
|
||||
|
||||
def _update_definitions(self):
|
||||
|
||||
# Fetch columns definitions from the table
|
||||
self.cursor.execute('PRAGMA table_info("' + self.table_name + '")')
|
||||
self._columns = {}
|
||||
for _, column, type_, notnull, hasdef, _ in self.cursor.fetchall():
|
||||
column = re.sub(r'^\"|\"$', '', column)
|
||||
match = re.match(r'(\w+)(\((.*?)\))?', type_)
|
||||
if match:
|
||||
typname = match.group(1).upper()
|
||||
size = match.group(3) and int(match.group(3)) or None
|
||||
else:
|
||||
typname = type_.upper()
|
||||
size = -1
|
||||
self._columns[column] = {
|
||||
'notnull': notnull,
|
||||
'hasdef': hasdef,
|
||||
'size': size,
|
||||
'typname': typname,
|
||||
}
|
||||
|
||||
# Fetch indexes defined for the table
|
||||
try:
|
||||
self.cursor.execute('PRAGMA index_list("' + self.table_name + '")')
|
||||
except IndexError: # There is sometimes IndexError
|
||||
self.cursor.execute('PRAGMA index_list("' + self.table_name + '")')
|
||||
self._indexes = [l[1] for l in self.cursor.fetchall()]
|
||||
|
||||
# Keep track of which module created each field
|
||||
self._field2module = {}
|
||||
if self.object_name is not None:
|
||||
self.cursor.execute('SELECT f.name, f.module '\
|
||||
'FROM ir_model_field f '\
|
||||
'JOIN ir_model m on (f.model=m.id) '\
|
||||
'WHERE m.model = %s',
|
||||
(self.object_name,)
|
||||
)
|
||||
for line in self.cursor.fetchall():
|
||||
self._field2module[line[0]] = line[1]
|
||||
|
||||
def alter_size(self, column_name, column_type):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to alter size of column %s ' \
|
||||
'of table %s not null!' % \
|
||||
(column_name, self.table_name))
|
||||
|
||||
def alter_type(self, column_name, column_type):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to alter type of column %s ' \
|
||||
'of table %s not null!' % \
|
||||
(column_name, self.table_name))
|
||||
|
||||
def db_default(self, column_name, value):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to set default on column %s ' \
|
||||
'of table %s not null!' % \
|
||||
(column_name, self.table_name))
|
||||
|
||||
def add_raw_column(self, column_name, column_type, column_format,
|
||||
default_fun=None, field_size=None, migrate=True):
|
||||
if self.column_exist(column_name):
|
||||
if not migrate:
|
||||
return
|
||||
base_type = column_type[0].upper()
|
||||
if base_type != self._columns[column_name]['typname']:
|
||||
if (self._columns[column_name]['typname'], base_type) in [
|
||||
('VARCHAR', 'TEXT'),
|
||||
('TEXT', 'VARCHAR'),
|
||||
('DATE', 'TIMESTAMP'),
|
||||
('INTEGER', 'FLOAT'),
|
||||
]:
|
||||
self.alter_type(column_name, base_type)
|
||||
else:
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to migrate column %s on table %s ' \
|
||||
'from %s to %s.' % \
|
||||
(column_name, self.table_name,
|
||||
self._columns[column_name]['typname'], base_type))
|
||||
|
||||
if base_type == 'VARCHAR' \
|
||||
and self._columns[column_name]['typname'] == 'VARCHAR':
|
||||
# Migrate size
|
||||
if field_size == None:
|
||||
if self._columns[column_name]['size'] > 0:
|
||||
self.alter_size(column_name, base_type)
|
||||
pass
|
||||
elif self._columns[column_name]['size'] == field_size:
|
||||
pass
|
||||
elif self._columns[column_name]['size'] > 0 and \
|
||||
self._columns[column_name]['size'] < field_size:
|
||||
self.alter_size(column_name, column_type[1])
|
||||
else:
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to migrate column %s on table %s ' \
|
||||
'from varchar(%s) to varchar(%s).' % \
|
||||
(column_name, self.table_name,
|
||||
self._columns[column_name]['size'] > 0 and \
|
||||
self._columns[column_name]['size'] or "",
|
||||
field_size))
|
||||
return
|
||||
|
||||
column_type = column_type[1]
|
||||
default = ''
|
||||
from trytond.model import fields
|
||||
if isinstance(self._model._columns[column_name], (fields.Integer, fields.Float)):
|
||||
default = ' DEFAULT 0'
|
||||
self.cursor.execute(('ALTER TABLE "%s" ADD COLUMN "%s" %s' + default) %
|
||||
(self.table_name, column_name, column_type))
|
||||
|
||||
if column_format:
|
||||
# check if table is non-empty:
|
||||
self.cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name)
|
||||
if self.cursor.fetchone():
|
||||
# Populate column with default values:
|
||||
default = None
|
||||
if default_fun is not None:
|
||||
default = default_fun(self.cursor, 0, {})
|
||||
self.cursor.execute('UPDATE "' + self.table_name + '" '\
|
||||
'SET "' + column_name + '" = %s',
|
||||
(column_format(default),))
|
||||
|
||||
self._update_definitions()
|
||||
|
||||
def add_fk(self, column_name, reference, on_delete=None):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to add foreign key on table %s!' % \
|
||||
(self.table_name,))
|
||||
|
||||
def index_action(self, column_name, action='add'):
|
||||
if isinstance(column_name, basestring):
|
||||
column_name = [column_name]
|
||||
index_name = self.table_name + "_" + '_'.join(column_name) + "_index"
|
||||
|
||||
if action == 'add':
|
||||
if index_name in self._indexes:
|
||||
return
|
||||
self.cursor.execute('CREATE INDEX "' + index_name + '" ' \
|
||||
'ON "' + self.table_name + '" ( ' + \
|
||||
','.join(['"' + x + '"' for x in column_name]) + \
|
||||
')')
|
||||
self._update_definitions()
|
||||
elif action == 'remove':
|
||||
if len(column_name) == 1:
|
||||
if self._field2module.get(column_name[0]) != self.module_name:
|
||||
return
|
||||
|
||||
if index_name in self._indexes:
|
||||
self.cursor.execute('DROP INDEX "%s" ' % (index_name,))
|
||||
self._update_definitions()
|
||||
else:
|
||||
raise Exception('Index action not supported!')
|
||||
|
||||
def not_null_action(self, column_name, action='add'):
|
||||
if not self.column_exist(column_name):
|
||||
return
|
||||
|
||||
if action == 'add':
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to set not null on column %s ' \
|
||||
'of table %s!' % \
|
||||
(column_name, self.table_name))
|
||||
elif action == 'remove':
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to remove not null on column %s ' \
|
||||
'of table %s!' % \
|
||||
(column_name, self.table_name))
|
||||
else:
|
||||
raise Exception('Not null action not supported!')
|
||||
|
||||
def add_constraint(self, ident, constraint, exception=False):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to add constraint on table %s!' % \
|
||||
(self.table_name,))
|
||||
|
||||
def drop_constraint(self, ident, exception=False):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to drop constraint on table %s!' % \
|
||||
(self.table_name,))
|
||||
|
||||
def drop_column(self, column_name, exception=False):
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to drop \'%s\' column on table %s!' % \
|
||||
(column_name, self.table_name))
|
||||
|
||||
@staticmethod
|
||||
def dropTable(cursor, model, table, cascade=False):
|
||||
cursor.execute('DELETE from ir_model_data where '\
|
||||
'model = \'%s\'' % model)
|
||||
|
||||
query = 'DROP TABLE "%s"' % table
|
||||
if cascade:
|
||||
query = query + ' CASCADE'
|
||||
cursor.execute(query)
|
||||
@@ -13,6 +13,7 @@ class ConfigManager(object):
|
||||
'xmlport': 8069,
|
||||
'webdav': False,
|
||||
'webdavport': 8080,
|
||||
'db_type': 'postgresql',
|
||||
'db_host': False,
|
||||
'db_port': False,
|
||||
'db_name': False,
|
||||
|
||||
Reference in new issue
Block a user