diff --git a/etc/trytond.conf b/etc/trytond.conf index e59eae6eac..5ee2171193 100644 --- a/etc/trytond.conf +++ b/etc/trytond.conf @@ -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 diff --git a/setup.py b/setup.py index 6164cdcfe4..4e8f504fe0 100644 --- a/setup.py +++ b/setup.py @@ -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'], diff --git a/trytond/backend/__init__.py b/trytond/backend/__init__.py index 0ce3f52a4e..7363cfc299 100644 --- a/trytond/backend/__init__.py +++ b/trytond/backend/__init__.py @@ -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 * diff --git a/trytond/backend/sqlite/__init__.py b/trytond/backend/sqlite/__init__.py new file mode 100644 index 0000000000..9598cf8965 --- /dev/null +++ b/trytond/backend/sqlite/__init__.py @@ -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 * diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py new file mode 100644 index 0000000000..11f75de48c --- /dev/null +++ b/trytond/backend/sqlite/database.py @@ -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)) diff --git a/trytond/backend/sqlite/fields.py b/trytond/backend/sqlite/fields.py new file mode 100644 index 0000000000..b0bf3aa19a --- /dev/null +++ b/trytond/backend/sqlite/fields.py @@ -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, +} diff --git a/trytond/backend/sqlite/init.sql b/trytond/backend/sqlite/init.sql new file mode 100644 index 0000000000..44cc5440b0 --- /dev/null +++ b/trytond/backend/sqlite/init.sql @@ -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 +); diff --git a/trytond/backend/sqlite/table.py b/trytond/backend/sqlite/table.py new file mode 100644 index 0000000000..e1cca47846 --- /dev/null +++ b/trytond/backend/sqlite/table.py @@ -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) diff --git a/trytond/config.py b/trytond/config.py index a671f335f3..3ca6583b84 100644 --- a/trytond/config.py +++ b/trytond/config.py @@ -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,