Add MySQL backend

This commit is contained in:
Cédric Krier
2010-02-25 16:43:04 +01:00
parent 80ad687daf
commit cb63509356
17 changed files with 1112 additions and 13 deletions
+1
View File
@@ -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
+1
View File
@@ -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/)
+20 -1
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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'],
+2
View File
@@ -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 *
+12 -3
View File
@@ -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
+6
View File
@@ -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 *
+305
View File
@@ -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)
+148
View File
@@ -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,
}
+193
View File
@@ -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;
+412
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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 ' \
+1 -2
View File
@@ -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)