Add MySQL backend
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Add MySQL backend
|
||||
* Refactoring by validation with pylint
|
||||
* Modify ondelete attribute of Many2One according to required attribute value
|
||||
* Refactor import_data of ModelStorage
|
||||
|
||||
@@ -10,6 +10,7 @@ Prerequisites
|
||||
* python-dateutil (http://labix.org/python-dateutil)
|
||||
* simplejson (http://undefined.org/python/#simplejson)
|
||||
* Optional: psycopg 2 or later (http://www.initd.org/)
|
||||
* Optional: MySQL-python (http://sourceforge.net/projects/mysql-python/)
|
||||
* Optional: pywebdav 0.9.3 or later (http://code.google.com/p/pywebdav/)
|
||||
* Optional: pydot (http://code.google.com/p/pydot/)
|
||||
* Optional: pytz (http://pytz.sourceforge.net/)
|
||||
|
||||
@@ -3,7 +3,7 @@ trytond
|
||||
|
||||
The server of the Tryton application platform.
|
||||
A three-tiers high-level general purpose application platform
|
||||
written in Python and use Postgresql as database engine.
|
||||
written in Python and use Postgresql as main database engine.
|
||||
It is the core base of an Open Source ERP.
|
||||
It provides modularity, scalability and security.
|
||||
|
||||
@@ -30,6 +30,25 @@ Package Contents
|
||||
trytond/
|
||||
trytond sources.
|
||||
|
||||
Database Backends
|
||||
-----------------
|
||||
|
||||
Tryton is mainly developed for PostgreSQL but there is some other backends
|
||||
available. Here is some warnings about using other backends:
|
||||
|
||||
* MySQL
|
||||
|
||||
* The lock implementation of MySQL requires that Tryton locks all tables.
|
||||
|
||||
* There are problems with floating-point comparisons.
|
||||
See: http://dev.mysql.com/doc/refman/5.5/en/problems-with-float.html
|
||||
|
||||
* Tryton uses a VARCHAR(255) for Char, Selection and Reference fields.
|
||||
|
||||
* MySQL can not create indexes containing text or blob fields.
|
||||
|
||||
* Tryton uses a DECIMAL(16, 8) for Decimal fields.
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
#webdavport = 8080
|
||||
|
||||
# Configure the database type
|
||||
# allowed values are postgresql, sqlite
|
||||
# allowed values are postgresql, sqlite, mysql
|
||||
db_type = postgresql
|
||||
|
||||
# Configure the database connection
|
||||
|
||||
@@ -54,6 +54,7 @@ setup(name=PACKAGE,
|
||||
] + SIMPLEJSON,
|
||||
extras_require={
|
||||
'PostgreSQL': ['psycopg2 >= 2.0'],
|
||||
'MySQL': ['MySQL-python'],
|
||||
'WebDAV': ['PyWebDAV >= 0.9.3'],
|
||||
'PDF': ['openoffice-python'],
|
||||
'SSL': ['pyOpenSSL'],
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
from trytond.config import CONFIG
|
||||
if CONFIG['db_type'] == 'sqlite':
|
||||
from sqlite import *
|
||||
elif CONFIG['db_type'] == 'mysql':
|
||||
from mysql import *
|
||||
else:
|
||||
from postgresql import *
|
||||
|
||||
@@ -186,11 +186,11 @@ class CursorInterface(object):
|
||||
:return: an integer
|
||||
'''
|
||||
|
||||
def has_lock(self):
|
||||
def lock(self, table):
|
||||
'''
|
||||
Return True if database handle lock table.
|
||||
Lock the table
|
||||
|
||||
:return: a boolean
|
||||
:param table: the table name
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -212,3 +212,12 @@ class CursorInterface(object):
|
||||
:return: a string
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def update_auto_increment(self, table, value):
|
||||
'''
|
||||
Update auto_increment value of table
|
||||
|
||||
:param table: the table name
|
||||
:param value: the auto_increment value
|
||||
'''
|
||||
pass
|
||||
|
||||
@@ -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,305 @@
|
||||
#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
|
||||
import MySQLdb
|
||||
import MySQLdb.cursors
|
||||
from MySQLdb import IntegrityError as DatabaseIntegrityError
|
||||
from MySQLdb import OperationalError as DatabaseOperationalError
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import tempfile
|
||||
|
||||
QUOTE_SEPARATION = re.compile(r"(.*?)('.*?')", re.DOTALL)
|
||||
DatabaseIntegrityError = None
|
||||
DatabaseOperationalError = None
|
||||
|
||||
|
||||
class Database(DatabaseInterface):
|
||||
|
||||
_list_cache = None
|
||||
_list_cache_timestamp = None
|
||||
|
||||
def connect(self):
|
||||
return self
|
||||
|
||||
def cursor(self, autocommit=False):
|
||||
args = {
|
||||
'db': self.database_name,
|
||||
'sql_mode': 'traditional,postgresql',
|
||||
'use_unicode': True,
|
||||
'charset': 'utf8',
|
||||
}
|
||||
if CONFIG['db_host']:
|
||||
args['host'] = CONFIG['db_host']
|
||||
if CONFIG['db_port']:
|
||||
args['port'] = int(CONFIG['db_port'])
|
||||
if CONFIG['db_user']:
|
||||
args['user'] = CONFIG['db_user']
|
||||
if CONFIG['db_password']:
|
||||
args['passwd'] = CONFIG['db_password']
|
||||
conn = MySQLdb.connect(**args)
|
||||
return Cursor(conn, self.database_name)
|
||||
|
||||
def close(self):
|
||||
return
|
||||
|
||||
def create(self, cursor, database_name):
|
||||
cursor.execute('CREATE DATABASE `' + database_name + '` ' \
|
||||
'DEFAULT CHARACTER SET = \'utf8\'')
|
||||
|
||||
def drop(self, cursor, database_name):
|
||||
cursor.execute('DROP DATABASE `' + database_name + '`')
|
||||
|
||||
@staticmethod
|
||||
def dump(database_name):
|
||||
from trytond.tools import exec_pg_command_pipe
|
||||
|
||||
cmd = ['mysqldump', '--no-create-db']
|
||||
if CONFIG['db_user']:
|
||||
cmd.append('--user=' + CONFIG['db_user'])
|
||||
if CONFIG['db_host']:
|
||||
cmd.append('--host=' + CONFIG['db_host'])
|
||||
if CONFIG['db_port']:
|
||||
cmd.append('--port=' + CONFIG['db_port'])
|
||||
if CONFIG['db_password']:
|
||||
cmd.append('--password=' + CONFIG['db_password'])
|
||||
cmd.append(database_name)
|
||||
|
||||
pipe = exec_pg_command_pipe(*tuple(cmd))
|
||||
pipe.stdin.close()
|
||||
data = pipe.stdout.read()
|
||||
res = pipe.wait()
|
||||
if res:
|
||||
raise Exception('Couldn\'t dump database!')
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def restore(database_name, data):
|
||||
from trytond.tools import exec_pg_command_pipe
|
||||
|
||||
database = Database().connect()
|
||||
cursor = database.cursor(autocommit=True)
|
||||
database.create(cursor, database_name)
|
||||
cursor.commit()
|
||||
cursor.close()
|
||||
|
||||
cmd = ['mysql']
|
||||
if CONFIG['db_user']:
|
||||
cmd.append('--user=' + CONFIG['db_user'])
|
||||
if CONFIG['db_host']:
|
||||
cmd.append('--host=' + CONFIG['db_host'])
|
||||
if CONFIG['db_port']:
|
||||
cmd.append('--port=' + CONFIG['db_port'])
|
||||
if CONFIG['db_password']:
|
||||
cmd.append('--password=' + CONFIG['db_password'])
|
||||
cmd.append(database_name)
|
||||
|
||||
fd, file_name = tempfile.mkstemp()
|
||||
fd = os.fdopen(fd, 'wb+')
|
||||
fd.write(data)
|
||||
fd.close()
|
||||
|
||||
cmd.append('<')
|
||||
cmd.append(file_name)
|
||||
|
||||
args2 = tuple(cmd)
|
||||
|
||||
pipe = exec_pg_command_pipe(*args2)
|
||||
pipe.stdin.close()
|
||||
res = pipe.wait()
|
||||
os.remove(file_name)
|
||||
if res:
|
||||
raise Exception('Couldn\'t restore database')
|
||||
|
||||
database = Database(database_name).connect()
|
||||
cursor = database.cursor()
|
||||
if not cursor.test():
|
||||
cursor.close()
|
||||
database.close()
|
||||
raise Exception('Couldn\'t restore database!')
|
||||
cursor.close()
|
||||
database.close()
|
||||
Database._list_cache = None
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def list(cursor):
|
||||
now = time.time()
|
||||
timeout = int(CONFIG['session_timeout'])
|
||||
res = Database._list_cache
|
||||
if res and abs(Database._list_cache_timestamp - now) < timeout:
|
||||
return res
|
||||
cursor.execute('SHOW DATABASES')
|
||||
res = []
|
||||
for db_name, in cursor.fetchall():
|
||||
try:
|
||||
database = Database(db_name).connect()
|
||||
except:
|
||||
continue
|
||||
cursor2 = database.cursor()
|
||||
if cursor2.test():
|
||||
res.append(db_name)
|
||||
cursor2.close(close=True)
|
||||
else:
|
||||
cursor2.close()
|
||||
database.close()
|
||||
Database._list_cache = res
|
||||
Database._list_cache_timestamp = now
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def init(cursor):
|
||||
from trytond.tools import safe_eval
|
||||
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
|
||||
for line in open(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 = safe_eval(open(tryton_file).read())
|
||||
active = info.get('active', False)
|
||||
if active:
|
||||
state = 'to install'
|
||||
else:
|
||||
state = 'uninstalled'
|
||||
cursor.execute('INSERT INTO ir_module_module ' \
|
||||
'(create_uid, create_date, author, website, name, ' \
|
||||
'shortdesc, description, state) ' \
|
||||
'VALUES (%s, now(), %s, %s, %s, %s, %s, %s)',
|
||||
(0, info.get('author', ''),
|
||||
info.get('website', ''), i, info.get('name', False),
|
||||
info.get('description', ''), state))
|
||||
cursor.execute('SELECT LAST_INSERT_ID()')
|
||||
module_id = cursor.fetchone()[0]
|
||||
dependencies = info.get('depends', [])
|
||||
for dependency in dependencies:
|
||||
cursor.execute('INSERT INTO ir_module_module_dependency ' \
|
||||
'(create_uid, create_date, module, name) ' \
|
||||
'VALUES (%s, now(), %s, %s)',
|
||||
(0, module_id, dependency))
|
||||
|
||||
|
||||
class _Cursor(MySQLdb.cursors.Cursor):
|
||||
|
||||
def __build_dict(self, row):
|
||||
return dict((desc[0], row[i])
|
||||
for i, desc in enumerate(self.description))
|
||||
|
||||
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]
|
||||
|
||||
def dictfetchall(self):
|
||||
rows = self.fetchall()
|
||||
return [self.__build_dict(row) for row in rows]
|
||||
|
||||
def fetchall(self):
|
||||
return list(super(_Cursor, self).fetchall())
|
||||
|
||||
|
||||
class Cursor(CursorInterface):
|
||||
|
||||
def __init__(self, conn, database_name):
|
||||
super(Cursor, self).__init__()
|
||||
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):
|
||||
buf = ""
|
||||
for nquote, quote in QUOTE_SEPARATION.findall(sql+"''"):
|
||||
nquote = nquote.replace('ilike', 'like')
|
||||
buf += nquote + quote
|
||||
sql = buf[:-2]
|
||||
try:
|
||||
if params:
|
||||
res = self.cursor.execute(sql, params)
|
||||
else:
|
||||
res = self.cursor.execute(sql)
|
||||
except Exception, exception:
|
||||
logger = logging.getLogger('sql')
|
||||
logger.error('Wrong SQL: ' + sql + ' ' + str(params))
|
||||
if isinstance(exception, MySQLdb.Error):
|
||||
exception.args = exception.args[1:]
|
||||
raise exception
|
||||
raise
|
||||
return res
|
||||
|
||||
def close(self, close=False):
|
||||
self.cursor.close()
|
||||
self.rollback()
|
||||
|
||||
def commit(self):
|
||||
super(Cursor, self).commit()
|
||||
self._conn.commit()
|
||||
|
||||
def rollback(self):
|
||||
super(Cursor, self).rollback()
|
||||
self._conn.rollback()
|
||||
|
||||
def test(self):
|
||||
self.cursor.execute("SHOW TABLES")
|
||||
for table, in self.cursor.fetchall():
|
||||
if table 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 True
|
||||
return False
|
||||
|
||||
def lastid(self):
|
||||
self.cursor.execute('SELECT LAST_INSERT_ID()')
|
||||
return self.cursor.fetchone()[0]
|
||||
|
||||
def lock(self, table):
|
||||
# Lock of table doesn't work because MySQL require
|
||||
# that the session locks all tables that will be accessed
|
||||
self.cursor.execute('FLUSH TABLES WITH READ LOCK')
|
||||
|
||||
def has_constraint(self):
|
||||
return True
|
||||
|
||||
def limit_clause(self, select, limit=None, offset=None):
|
||||
if offset and limit is None:
|
||||
limit = 18446744073709551610 #max bigint
|
||||
if limit is not None:
|
||||
select += ' LIMIT %d' % limit
|
||||
if offset is not None and offset != 0:
|
||||
select += ' OFFSET %d' % offset
|
||||
return select
|
||||
|
||||
def update_auto_increment(self, table, value):
|
||||
self.cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table,
|
||||
value)
|
||||
@@ -0,0 +1,148 @@
|
||||
#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 import fields
|
||||
|
||||
|
||||
class Boolean(fields.Boolean):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('bool', 'bool')
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return value and '1' or '0'
|
||||
|
||||
|
||||
class Integer(fields.Integer):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('bigint', 'bigint')
|
||||
|
||||
|
||||
class BigInteger(fields.BigInteger):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('bigint', 'bigint')
|
||||
|
||||
|
||||
class Char(fields.Char):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
if field.size:
|
||||
return ('varchar', 'varchar(%d)' % (field.size,))
|
||||
return ('varchar', 'varchar(255)')
|
||||
|
||||
|
||||
class Sha(fields.Sha):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('varchar', 'varchar(40)')
|
||||
|
||||
|
||||
class Text(fields.Text):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('text', 'text')
|
||||
|
||||
|
||||
class Float(fields.Float):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('float', 'float')
|
||||
|
||||
|
||||
class Numeric(fields.Numeric):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('decimal', 'decimal(16, 8)')
|
||||
|
||||
|
||||
class Date(fields.Date):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('date', 'date')
|
||||
|
||||
|
||||
class DateTime(fields.DateTime):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('timestamp', 'timestamp null')
|
||||
|
||||
|
||||
class Timestamp(fields.Timestamp):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('timestamp', 'timestamp null')
|
||||
|
||||
|
||||
class Time(fields.Time):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('time', 'time')
|
||||
|
||||
|
||||
class Binary(fields.Binary):
|
||||
|
||||
@staticmethod
|
||||
def sql_format(value):
|
||||
return value or None
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('longblob', 'longblob')
|
||||
|
||||
|
||||
class Selection(fields.Selection):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('varchar', 'varchar(255)')
|
||||
|
||||
|
||||
class Reference(fields.Reference):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('varchar', 'varchar(255)')
|
||||
|
||||
|
||||
class Many2One(fields.Many2One):
|
||||
|
||||
@staticmethod
|
||||
def sql_type(field):
|
||||
return ('bigint', 'bigint')
|
||||
|
||||
FIELDS = {
|
||||
'boolean': Boolean,
|
||||
'integer': Integer,
|
||||
'biginteger': BigInteger,
|
||||
'char': Char,
|
||||
'sha': Sha,
|
||||
'text': Text,
|
||||
'float': Float,
|
||||
'numeric': Numeric,
|
||||
'date': Date,
|
||||
'datetime': DateTime,
|
||||
'timestamp': Timestamp,
|
||||
'time': Time,
|
||||
'binary': Binary,
|
||||
'selection': Selection,
|
||||
'reference': Reference,
|
||||
'many2one': Many2One,
|
||||
'one2many': fields.One2Many,
|
||||
'many2many': fields.Many2Many,
|
||||
'function': fields.Function,
|
||||
'property': fields.Property,
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
CREATE TABLE ir_model (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
model VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
info TEXT,
|
||||
module VARCHAR(255),
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE ir_model_field (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
model BIGINT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
relation VARCHAR(255),
|
||||
field_description VARCHAR(255),
|
||||
ttype VARCHAR(255),
|
||||
help VARCHAR(255),
|
||||
module VARCHAR(255),
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT ir_model_field_model_fkey FOREIGN KEY (model) REFERENCES ir_model (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE ir_ui_view (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
model VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(255),
|
||||
arch TEXT NOT NULL,
|
||||
field_childs VARCHAR(255),
|
||||
priority BIGINT NOT NULL default 0,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE ir_ui_menu (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
parent BIGINT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
icon VARCHAR(255),
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT ir_ui_menu_parent_fkey FOREIGN KEY (parent) REFERENCES ir_ui_menu (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE ir_translation (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
lang VARCHAR(255),
|
||||
src TEXT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
res_id BIGINT NOT NULL DEFAULT 0,
|
||||
value TEXT,
|
||||
type VARCHAR(255),
|
||||
module VARCHAR(255),
|
||||
fuzzy BOOLEAN NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE ir_lang (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
translatable BOOLEAN NOT NULL,
|
||||
active BOOLEAN NOT NULL,
|
||||
direction VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE res_user (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
active BOOLEAN NOT NULL,
|
||||
login VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(40),
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
ALTER TABLE res_user ADD CONSTRAINT res_user_login_uniq UNIQUE (login);
|
||||
|
||||
INSERT INTO res_user (id, login, password, name, active) VALUES (0, 'root', NULL, 'Root', False);
|
||||
UPDATE res_user set id = 0;
|
||||
|
||||
CREATE TABLE res_group (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE `res_user-res_group` (
|
||||
uid BIGINT NOT NULL,
|
||||
gid BIGINT NOT NULL,
|
||||
CONSTRAINT `res_user-res_group_uid_fkey` FOREIGN KEY (uid) REFERENCES res_user (id) ON DELETE CASCADE,
|
||||
CONSTRAINT `res_user-res_group_gid_fkey` FOREIGN KEY (gid) REFERENCES res_group (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE wkf (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
name VARCHAR(255),
|
||||
model VARCHAR(255),
|
||||
on_create BOOLEAN NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE wkf_activity (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
workflow BIGINT,
|
||||
subflow BIGINT,
|
||||
split_mode VARCHAR(255),
|
||||
join_mode VARCHAR(255),
|
||||
kind VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
signal_send VARCHAR(255),
|
||||
flow_start BOOLEAN NOT NULL,
|
||||
flow_stop BOOLEAN NOT NULL,
|
||||
action TEXT,
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT wkf_activity_workflow_fkey FOREIGN KEY (workflow) REFERENCES wkf (id) ON DELETE CASCADE,
|
||||
CONSTRAINT wkf_activity_subflow_fkey FOREIGN KEY (subflow) REFERENCES wkf (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE wkf_transition (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
act_from BIGINT,
|
||||
act_to BIGINT,
|
||||
`condition` VARCHAR(255),
|
||||
trigger_expr_id VARCHAR(255),
|
||||
signal VARCHAR(255),
|
||||
`group` BIGINT,
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT wkf_transition_act_from_fkey FOREIGN KEY (act_from) REFERENCES wkf_activity (id) ON DELETE CASCADE,
|
||||
CONSTRAINT wkf_transition_act_to_fkey FOREIGN KEY (act_to) REFERENCES wkf_activity (id) ON DELETE CASCADE,
|
||||
CONSTRAINT wkf_transition_group_fkey FOREIGN KEY (`group`) REFERENCES res_group (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE wkf_instance (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
workflow BIGINT,
|
||||
uid BIGINT DEFAULT 0,
|
||||
res_id BIGINT NOT NULL DEFAULT 0,
|
||||
res_type VARCHAR(255) NOT NULL,
|
||||
state VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT wkf_instance_workflow_fkey FOREIGN KEY (workflow) REFERENCES wkf (id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE wkf_workitem (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
activity BIGINT,
|
||||
instance BIGINT,
|
||||
subflow BIGINT,
|
||||
state VARCHAR(255),
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT wkf_workitem_activity_fkey FOREIGN KEY (activity) REFERENCES wkf_activity (id) ON DELETE CASCADE,
|
||||
CONSTRAINT wkf_workitem_instance_fkey FOREIGN KEY (instance) REFERENCES wkf_instance (id) ON DELETE CASCADE,
|
||||
CONSTRAINT wkf_workitem_subflow_fkey FOREIGN KEY (subflow) REFERENCES wkf_instance (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE wkf_witm_trans (
|
||||
trans_id BIGINT,
|
||||
inst_id BIGINT,
|
||||
CONSTRAINT wkf_witm_trans_trans_id_fkey FOREIGN KEY (trans_id) REFERENCES wkf_transition (id) ON DELETE CASCADE,
|
||||
CONSTRAINT wkf_witm_trans_inst_id_fkey FOREIGN KEY (inst_id) REFERENCES wkf_instance (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE ir_module_module (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
create_uid BIGINT,
|
||||
create_date TIMESTAMP NOT NULL,
|
||||
write_date TIMESTAMP NULL,
|
||||
write_uid BIGINT,
|
||||
website VARCHAR(255),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
author VARCHAR(255),
|
||||
url VARCHAR(255),
|
||||
state VARCHAR(255),
|
||||
shortdesc VARCHAR(255),
|
||||
description TEXT,
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT ir_module_module_create_uid_fkey FOREIGN KEY (create_uid) REFERENCES res_user (id) ON DELETE SET NULL,
|
||||
CONSTRAINT ir_module_module_write_uid_fkey FOREIGN KEY (write_uid) REFERENCES res_user (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
ALTER TABLE ir_module_module ADD CONSTRAINT name_uniq UNIQUE (name);
|
||||
|
||||
CREATE TABLE ir_module_module_dependency (
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
create_uid BIGINT,
|
||||
create_date TIMESTAMP NOT NULL,
|
||||
write_date TIMESTAMP NULL,
|
||||
write_uid BIGINT,
|
||||
name VARCHAR(255),
|
||||
module BIGINT,
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT ir_module_module_dependency_create_uid_fkey FOREIGN KEY (create_uid) REFERENCES res_user (id) ON DELETE SET NULL,
|
||||
CONSTRAINT ir_module_module_dependency_write_uid_fkey FOREIGN KEY (write_uid) REFERENCES res_user (id) ON DELETE SET NULL,
|
||||
CONSTRAINT ir_module_module_dependency_module_fkey FOREIGN KEY (module) REFERENCES ir_module_module (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
@@ -0,0 +1,412 @@
|
||||
#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
|
||||
import logging
|
||||
|
||||
|
||||
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._fkeys = []
|
||||
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 BIGINT AUTO_INCREMENT NOT NULL, ' \
|
||||
'PRIMARY KEY(id)' \
|
||||
') ENGINE=InnoDB;' % self.table_name)
|
||||
else:
|
||||
self.cursor.execute('CREATE TABLE `%s` (' \
|
||||
'__id BIGINT AUTO_INCREMENT NOT NULL, ' \
|
||||
'id BIGINT, ' \
|
||||
'PRIMARY KEY(__id)' \
|
||||
') ENGINE=InnoDB;' % self.table_name)
|
||||
|
||||
self._update_definitions()
|
||||
if 'id' not in self._columns:
|
||||
if not self.history:
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'ADD COLUMN id BIGINT AUTO_INCREMENT ' \
|
||||
'NOT NULL PRIMARY KEY' %
|
||||
self.table_name)
|
||||
else:
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'ADD COLUMN id BIGINT' % self.table_name)
|
||||
self._update_definitions()
|
||||
if self.history and not '__id' in self._columns:
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'ADD COLUMN __id BIGINT AUTO_INCREMENT ' \
|
||||
'NOT NULL PRIMARY KEY' %
|
||||
self.table_name)
|
||||
self._update_definitions()
|
||||
|
||||
@staticmethod
|
||||
def table_exist(cursor, table_name):
|
||||
cursor.execute("SELECT table_name FROM information_schema.tables " \
|
||||
"WHERE table_schema = %s AND table_name = %s",
|
||||
(cursor.database_name, table_name))
|
||||
return bool(cursor.rowcount)
|
||||
|
||||
@staticmethod
|
||||
def table_rename(cursor, old_name, new_name):
|
||||
#Rename table
|
||||
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))
|
||||
#Rename history table
|
||||
old_history = old_name + '__history'
|
||||
new_history = new_name + '__history'
|
||||
if TableHandler.table_exist(cursor, old_history) and \
|
||||
not TableHandler.table_exist(cursor, new_history):
|
||||
cursor.execute('ALTER TABLE `%s` RENAME TO `%s`' % \
|
||||
(old_history, new_history))
|
||||
|
||||
@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 column_rename(self, old_name, new_name, exception=False):
|
||||
if self.column_exist(old_name) and \
|
||||
not self.column_exist(new_name):
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'RENAME COLUMN `%s` TO `%s`' %
|
||||
(self.table_name, old_name, new_name))
|
||||
elif exception and self.column_exist(new_name):
|
||||
raise Exception('Unable to rename column %s.%s to %s.%s: ' \
|
||||
'%s.%s already exist!' % \
|
||||
(self.table_name, old_name, self.table_name, new_name,
|
||||
self.table_name, new_name))
|
||||
|
||||
def _update_definitions(self):
|
||||
# Fetch columns definitions from the table
|
||||
self.cursor.execute("SELECT column_name, character_maximum_length, " \
|
||||
"data_type, is_nullable, column_default " \
|
||||
"FROM information_schema.columns " \
|
||||
"WHERE table_schema = %s AND table_name = %s",
|
||||
(self.cursor.database_name, self.table_name))
|
||||
self._columns = {}
|
||||
for line in self.cursor.fetchall():
|
||||
column, size, typname, nullable, default = line
|
||||
self._columns[column] = {
|
||||
'size': size,
|
||||
'typname': typname,
|
||||
'nullable': nullable == 'YES' and True or False,
|
||||
'default': default,
|
||||
}
|
||||
|
||||
# fetch constraints for the table
|
||||
self.cursor.execute("SELECT constraint_name, constraint_type " \
|
||||
"FROM information_schema.table_constraints " \
|
||||
"WHERE table_schema = %s AND table_name = %s",
|
||||
(self.cursor.database_name, self.table_name))
|
||||
self._constraints = []
|
||||
self._fkeys = []
|
||||
for line in self.cursor.fetchall():
|
||||
conname, contype = line
|
||||
if contype not in ('PRIMARY KEY', 'FOREIGN KEY'):
|
||||
self._constraints.append(conname)
|
||||
elif contype == 'FOREIGN KEY':
|
||||
self._fkeys.append(conname)
|
||||
|
||||
# Fetch indexes defined for the table
|
||||
self.cursor.execute('SHOW INDEXES FROM `%s`' % self.table_name)
|
||||
self._indexes = list(set(x[2] for x in self.cursor.fetchall()
|
||||
if x[2] != 'PRIMARY'))
|
||||
|
||||
# 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):
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'MODIFY COLUMN `%s` %s' % \
|
||||
(self.table_name, column_name,
|
||||
self._column_definition(column_name)))
|
||||
self._update_definitions()
|
||||
|
||||
def alter_type(self, column_name, column_type):
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'MODIFY COLUMN `%s` %s' % \
|
||||
(self.table_name, column_name,
|
||||
self._column_definition(column_name, typname=column_type)))
|
||||
self._update_definitions()
|
||||
|
||||
def db_default(self, column_name, value):
|
||||
self.cursor.execute('ALTER TABLE `%s` '
|
||||
'MODIFY COLUMN `%s` %s' % \
|
||||
(self.table_name, column_name,
|
||||
self._column_definition(column_name,
|
||||
default=value)))
|
||||
self._update_definitions()
|
||||
|
||||
def add_raw_column(self, column_name, column_type, column_format,
|
||||
default_fun=None, field_size=None, migrate=True, string=''):
|
||||
if self.column_exist(column_name):
|
||||
if not migrate:
|
||||
return
|
||||
base_type = column_type[0].lower()
|
||||
if base_type != self._columns[column_name]['typname']:
|
||||
if (self._columns[column_name]['typname'], base_type) in (
|
||||
('varchar', 'text'),
|
||||
('text', 'varchar'),
|
||||
('date', 'timestamp'),
|
||||
('bigint', 'float'),
|
||||
('int', 'bigint'),
|
||||
('tinyint', 'bool'),
|
||||
('decimal', 'numeric'),
|
||||
):
|
||||
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 is None:
|
||||
if self._columns[column_name]['size'] != 255:
|
||||
self.alter_size(column_name, base_type)
|
||||
elif self._columns[column_name]['size'] == field_size:
|
||||
pass
|
||||
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 255,
|
||||
field_size))
|
||||
return
|
||||
|
||||
column_type = column_type[1]
|
||||
self.cursor.execute('ALTER TABLE `%s` ADD COLUMN `%s` %s' %
|
||||
(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.rowcount:
|
||||
# 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):
|
||||
if on_delete is None:
|
||||
on_delete = 'SET NULL'
|
||||
conname = '%s_%s_fkey' % (self.table_name, column_name)
|
||||
if conname in self._fkeys:
|
||||
self.drop_fk(column_name)
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'ADD CONSTRAINT `%s` FOREIGN KEY (`%s`) ' \
|
||||
'REFERENCES `%s` (id) ON DELETE %s' %
|
||||
(self.table_name, conname, column_name, reference, on_delete))
|
||||
self._update_definitions()
|
||||
|
||||
def drop_fk(self, column_name, table=None):
|
||||
conname = '%s_%s_fkey' % (self.table_name, column_name)
|
||||
if conname not in self._fkeys:
|
||||
return
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'DROP FOREIGN KEY `%s`' % (self.table_name, conname))
|
||||
self._update_definitions()
|
||||
|
||||
def index_action(self, column_name, action='add', table=None):
|
||||
if isinstance(column_name, basestring):
|
||||
column_name = [column_name]
|
||||
index_name = (table or self.table_name) + "_" + '_'.join(column_name) \
|
||||
+ "_index"
|
||||
|
||||
for k in column_name:
|
||||
if k in self._columns:
|
||||
if self._columns[k]['typname'] in ('text', 'blob'):
|
||||
return
|
||||
|
||||
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) \
|
||||
!= 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':
|
||||
if not self._columns[column_name]['nullable']:
|
||||
return
|
||||
self.cursor.execute('SELECT id FROM `%s` ' \
|
||||
'WHERE `%s` IS NULL' % \
|
||||
(self.table_name, column_name))
|
||||
if not self.cursor.rowcount:
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'MODIFY COLUMN `%s` %s' %
|
||||
(self.table_name, column_name,
|
||||
self._column_definition(column_name,
|
||||
nullable=False)))
|
||||
self._update_definitions()
|
||||
else:
|
||||
logging.getLogger('init').warning(
|
||||
'Unable to set column %s ' \
|
||||
'of table %s not null !\n'\
|
||||
'Try to re-run: ' \
|
||||
'trytond.py --update=module\n' \
|
||||
'If it doesn\'t work, update records ' \
|
||||
'and execute manually:\n' \
|
||||
'ALTER TABLE `%s` MODIFY COLUMN `%s` %s' % \
|
||||
(column_name, self.table_name, self.table_name,
|
||||
column_name, self._column_definition(column_name,
|
||||
nullable=False)))
|
||||
elif action == 'remove':
|
||||
if self._columns[column_name]['nullable']:
|
||||
return
|
||||
if self._field2module.get(column_name, self.module_name) \
|
||||
!= self.module_name:
|
||||
return
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'MODIFY COLUMN `%s` %s' %
|
||||
(self.table_name, column_name,
|
||||
self._column_definition(column_name, nullable=True)))
|
||||
self._update_definitions()
|
||||
else:
|
||||
raise Exception('Not null action not supported!')
|
||||
|
||||
def add_constraint(self, ident, constraint, exception=False):
|
||||
ident = self.table_name + "_" + ident
|
||||
if ident in self._constraints:
|
||||
# This constrain already exists
|
||||
return
|
||||
try:
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'ADD CONSTRAINT `%s` %s' % \
|
||||
(self.table_name, ident, constraint,))
|
||||
except:
|
||||
if exception:
|
||||
raise
|
||||
logging.getLogger('init').warning(
|
||||
'unable to add \'%s\' constraint on table %s !\n' \
|
||||
'If you want to have it, you should update the records ' \
|
||||
'and execute manually:\n'\
|
||||
'ALTER table `%s` ADD CONSTRAINT `%s` %s' % \
|
||||
(constraint, self.table_name, self.table_name,
|
||||
ident, constraint,))
|
||||
self._update_definitions()
|
||||
|
||||
def drop_constraint(self, ident, exception=False, table=None):
|
||||
ident = (table or self.table_name) + "_" + ident
|
||||
if ident not in self._constraints:
|
||||
return
|
||||
try:
|
||||
self.cursor.execute('ALTER TABLE `%s` ' \
|
||||
'DROP CONSTRAINT `%s`' % \
|
||||
(self.table_name, ident))
|
||||
except:
|
||||
if exception:
|
||||
raise
|
||||
logging.getLogger('init').warning(
|
||||
'unable to drop \'%s\' constraint on table %s!' % \
|
||||
(ident, self.table_name))
|
||||
self._update_definitions()
|
||||
|
||||
def drop_column(self, column_name, exception=False):
|
||||
if not self.column_exist(column_name):
|
||||
return
|
||||
try:
|
||||
self.cursor.execute(
|
||||
'ALTER TABLE `%s` DROP COLUMN `%s`' %
|
||||
(self.table_name, column_name))
|
||||
|
||||
except:
|
||||
if exception:
|
||||
raise
|
||||
logging.getLogger('init').warning(
|
||||
'unable to drop \'%s\' column on table %s!' % \
|
||||
(column_name, self.table_name))
|
||||
self._update_definitions()
|
||||
|
||||
@staticmethod
|
||||
def drop_table(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)
|
||||
|
||||
def _column_definition(self, column_name, typname=None, nullable=None,
|
||||
size=None, default=None):
|
||||
if typname is None:
|
||||
typname = self._columns[column_name]['typname']
|
||||
if nullable is None:
|
||||
nullable = self._columns[column_name]['nullable']
|
||||
if size is None:
|
||||
size = self._columns[column_name]['size']
|
||||
if default is None:
|
||||
default = self._columns[column_name]['default']
|
||||
res = ''
|
||||
if typname == 'varchar':
|
||||
if int(size) > 255:
|
||||
size = 255
|
||||
res = 'varchar(%s)' % str(size)
|
||||
elif typname == 'decimal':
|
||||
res = 'decimal(16, 8)'
|
||||
else:
|
||||
res = typname
|
||||
# Default value for timestamp doesn't work
|
||||
if typname == 'timestamp' and not nullable:
|
||||
nullable = True
|
||||
if nullable:
|
||||
res += ' NULL'
|
||||
else:
|
||||
res += ' NOT NULL'
|
||||
if default is not None:
|
||||
res += ' DEFAULT %s' % default
|
||||
return res
|
||||
@@ -380,8 +380,8 @@ class Cursor(CursorInterface):
|
||||
self.cursor.execute('SELECT last_value FROM "' + table + '_id_seq"')
|
||||
return self.cursor.fetchone()[0]
|
||||
|
||||
def has_lock(self):
|
||||
return True
|
||||
def lock(self, table):
|
||||
self.cursor.execute('LOCK "%s"' % table)
|
||||
|
||||
def has_constraint(self):
|
||||
return True
|
||||
|
||||
@@ -328,8 +328,8 @@ class Cursor(CursorInterface):
|
||||
self.cursor.execute('SELECT last_insert_rowid()')
|
||||
return self.cursor.fetchone()[0]
|
||||
|
||||
def has_lock(self):
|
||||
return False
|
||||
def lock(self, table):
|
||||
pass
|
||||
|
||||
def has_constraint(self):
|
||||
return False
|
||||
|
||||
@@ -349,6 +349,7 @@ class ActionReport(ModelSQL, ModelView):
|
||||
new_id = report.action.id
|
||||
cursor.execute('UPDATE "' + self._table + '" SET id = %s ' \
|
||||
'WHERE id = %s', (report.action.id, report.id))
|
||||
cursor.update_auto_increment(self._table, report.action.id)
|
||||
ModelStorage.delete(self, cursor, user, report.id)
|
||||
self.write(cursor, user, new_id, later, context=context)
|
||||
return new_id
|
||||
@@ -541,6 +542,7 @@ class ActionActWindow(ModelSQL, ModelView):
|
||||
new_id = act_window.action.id
|
||||
cursor.execute('UPDATE "' + self._table + '" SET id = %s ' \
|
||||
'WHERE id = %s', (act_window.action.id, act_window.id))
|
||||
cursor.update_auto_increment(self._table, act_window.action.id)
|
||||
ModelStorage.delete(self, cursor, user, act_window.id)
|
||||
self.write(cursor, user, new_id, later, context=context)
|
||||
return new_id
|
||||
@@ -618,6 +620,7 @@ class ActionWizard(ModelSQL, ModelView):
|
||||
new_id = wizard.action.id
|
||||
cursor.execute('UPDATE "' + self._table + '" SET id = %s ' \
|
||||
'WHERE id = %s', (wizard.action.id, wizard.id))
|
||||
cursor.update_auto_increment(self._table, wizard.action.id)
|
||||
ModelStorage.delete(self, cursor, user, wizard.id)
|
||||
self.write(cursor, user, new_id, later, context=context)
|
||||
return new_id
|
||||
@@ -763,6 +766,7 @@ class ActionURL(ModelSQL, ModelView):
|
||||
new_id = url.action.id
|
||||
cursor.execute('UPDATE "' + self._table + '" SET id = %s ' \
|
||||
'WHERE id = %s', (url.action.id, url.id))
|
||||
cursor.update_auto_increment(self._table, url.action.id)
|
||||
ModelStorage.delete(self, cursor, user, url.id)
|
||||
self.write(cursor, user, new_id, later, context=context)
|
||||
return new_id
|
||||
|
||||
+1
-2
@@ -155,8 +155,7 @@ class Cron(ModelSQL, ModelView):
|
||||
return
|
||||
try:
|
||||
try:
|
||||
if cursor.has_lock():
|
||||
cursor.execute('LOCK TABLE ir_cron')
|
||||
cursor.lock(self._table)
|
||||
cursor.execute('SELECT * FROM ir_cron ' \
|
||||
'WHERE numbercall <> 0 ' \
|
||||
'AND active ' \
|
||||
|
||||
@@ -149,8 +149,7 @@ class SequenceStrict(Sequence):
|
||||
_description = __doc__
|
||||
|
||||
def get_id(self, cursor, user, clause, context=None):
|
||||
if cursor.has_lock():
|
||||
cursor.execute('LOCK TABLE "' + self._table + '"')
|
||||
cursor.lock(self._table)
|
||||
return super(SequenceStrict, self).get_id(cursor, user, clause,
|
||||
context=context)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user