diff --git a/trytond/ir/model.py b/trytond/ir/model.py
index 347be1c9d2..c3db8ae072 100644
--- a/trytond/ir/model.py
+++ b/trytond/ir/model.py
@@ -37,6 +37,8 @@ class ModelField(OSV):
group_name = fields.Char('Group Name', size=None)
view_load = fields.Boolean('View Auto-Load')
help = fields.Text('Help', translate=True)
+ module = fields.Char('Module', size=None,
+ help="Module in which this field is defined")
def default_relate(self, cursor, user, context=None):
return 0
diff --git a/trytond/ir/model.xml b/trytond/ir/model.xml
index b6e46b5a3d..d698623a3b 100644
--- a/trytond/ir/model.xml
+++ b/trytond/ir/model.xml
@@ -83,6 +83,8 @@
+
+
]]>
@@ -98,6 +100,7 @@
+
]]>
diff --git a/trytond/osv/orm.py b/trytond/osv/orm.py
index 077674b355..50eaa78fb8 100644
--- a/trytond/osv/orm.py
+++ b/trytond/osv/orm.py
@@ -9,6 +9,7 @@ import traceback
import datetime
from lxml import etree
import copy
+from trytond.sql_db import table_handler
ID_MAX = 1000
@@ -265,13 +266,13 @@ class ORM(object):
cursor.execute('SELECT f.id AS id, f.name AS name, ' \
'f.field_description AS field_description, ' \
'f.ttype AS ttype, f.relation AS relation, ' \
- 'f.group_name AS group_name, f.view_load AS view_load ' \
+ 'f.group_name AS group_name, f.view_load AS view_load, ' \
+ 'f.module as module '\
'FROM ir_model_field AS f, ir_model AS m ' \
'WHERE f.model = m.id ' \
'AND m.model = %s ' \
- 'AND f.name in ' \
- '(' + ','.join(['%s' for x in self._columns]) + ')',
- (self._name,) + tuple(self._columns))
+ 'AND f.module = %s ',
+ (self._name, module_name))
columns = {}
for column in cursor.dictfetchall():
columns[column['name']] = column
@@ -299,12 +300,12 @@ class ORM(object):
if k not in columns:
cursor.execute("INSERT INTO ir_model_field " \
"(model, name, field_description, ttype, " \
- "relation, group_name, view_load, help) " \
- "VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
+ "relation, group_name, view_load, help, module) " \
+ "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
(model_id, k, field.string, field._type,
field._obj or '', field.group_name or '',
(field.view_load and 'True') or 'False',
- field.help))
+ field.help, module_name))
elif columns[k]['field_description'] != field.string \
or columns[k]['ttype'] != field._type \
or columns[k]['relation'] != (field._obj or '') \
@@ -361,6 +362,13 @@ class ORM(object):
'VALUES (%s, %s, %s, %s, %s, %s)',
(trans_name, 'en_US', 'selection', val, '',
module_name))
+ # Clean ir_model_field from field that are no more existing.
+ for k in columns:
+ if columns[k]['module'] == module_name and k not in self._columns:
+ # XXX This delete field even when it is defined later in the module
+ cursor.execute('DELETE FROM ir_model_field '\
+ 'WHERE id = %s',
+ (columns[k]['id'],))
def auto_init(self, cursor, module_name):
self.init(cursor, module_name)
@@ -383,372 +391,92 @@ class ORM(object):
'VALUES (%s, %s, %s, %s, %s, %s)',
(self._name, 'en_US', 'error', error, '', module_name))
+
+
def _auto_init(self, cursor, module_name):
logger = Logger()
- create = False
-
self._field_create(cursor, module_name)
- if self._auto and not self.table_query():
- cursor.execute("SELECT relname FROM pg_class " \
- "WHERE relkind in ('r', 'v') AND relname = %s",
- (self._table,))
- if not cursor.rowcount:
- cursor.execute("CREATE TABLE \"%s\" " \
- "(id SERIAL NOT NULL, " \
- "PRIMARY KEY(id))" % self._table)
- create = True
- if self._log_access:
- logs = {
- 'create_uid': 'INTEGER REFERENCES res_user ' \
- 'ON DELETE SET NULL',
- 'create_date': 'TIMESTAMP',
- 'write_uid': 'INTEGER REFERENCES res_user ' \
- 'ON DELETE SET NULL',
- 'write_date': 'TIMESTAMP'
- }
- for k in logs:
- cursor.execute("SELECT c.relname " \
- "FROM pg_class c, pg_attribute a " \
- "WHERE c.relname = %s " \
- "AND a.attname = %s " \
- "AND c.oid = a.attrelid",
- (self._table, k))
- if not cursor.rowcount:
- cursor.execute("ALTER TABLE \"%s\" " \
- "ADD COLUMN \"%s\" %s" %
- (self._table, k, logs[k]))
+ if not self._auto or self.table_query():
+ # No db table for this object.
+ return
- # iterate on the database columns to drop the NOT NULL constraints
- # of fields which were required but have been removed
- cursor.execute(
- "SELECT a.attname, a.attnotnull "\
- "FROM pg_class c, pg_attribute a "\
- "WHERE c.oid=a.attrelid AND c.relname='%s'" % self._table)
- db_columns = cursor.dictfetchall()
- for column in db_columns:
- if column['attname'] not in (
- 'id',
- 'oid',
- 'tableoid',
- 'ctid',
- 'xmin',
- 'xmax',
- 'cmin',
- 'cmax',
- ):
- if column['attnotnull'] \
- and (column['attname'] not in self._columns):
- cursor.execute("ALTER TABLE \"%s\" " \
- "ALTER COLUMN \"%s\" DROP NOT NULL" % \
- (self._table, column['attname']))
+ table = table_handler(cursor, self._table, self._name, module_name)
+ if self._log_access:
+ logs = (
+ ('create_date', 'timestamp', 'TIMESTAMP'),
+ ('write_date', 'timestamp', 'TIMESTAMP'),
+ ('create_uid', 'int4',
+ 'INTEGER REFERENCES res_user ON DELETE SET NULL'),
+ ('write_uid', 'int4',
+ 'INTEGER REFERENCES res_user ON DELETE SET NULL'),
+ )
+ for log in logs:
+ table.add_raw_column(log[0], (log[1], log[2]), migrate=False)
+ for field_name, field in self._columns.iteritems():
+ default_fun = None
+ if field_name in (
+ 'id',
+ 'write_uid',
+ 'write_date',
+ 'create_uid',
+ 'create_date',
+ ):
+ continue
- # iterate on the "object columns"
- for k in self._columns:
- if k in (
- 'id',
- 'write_uid',
- 'write_date',
- 'create_uid',
- 'create_date',
- ):
- continue
+ if field._classic_write:
+ if isinstance(field, (fields.Integer, fields.Float)):
+ default = 0 #XXX why ?
- field = self._columns[k]
- if isinstance(field, fields.One2Many):
- cursor.execute("SELECT relname FROM pg_class " \
- "WHERE relkind = 'r' AND relname = %s",
- (field._obj,))
- if cursor.fetchone():
- cursor.execute("SELECT count(*) as c " \
- "FROM pg_class c, pg_attribute a " \
- "WHERE c.relname = %s " \
- "AND a.attname = %s " \
- "AND c.oid = a.attrelid",
- (field._obj, field._field))
- (res,) = cursor.fetchone()
- if not res:
- cursor.execute("ALTER TABLE \"%s\" " \
- "ADD FOREIGN KEY (%s) " \
- "REFERENCES \"%s\" ON DELETE SET NULL" % \
- (self._obj, field._field, field._table))
- elif isinstance(field, fields.Many2Many):
- cursor.execute("SELECT relname FROM pg_class " \
- "WHERE relkind in ('r','v') AND relname=%s",
- (field._rel,))
- if not cursor.dictfetchall():
- # for some fields in ir are linked to res objects
- # that are not yet in the pooler
- if field._obj in ('res.user', 'res.group'):
- ref = field._obj.replace('.','_')
- else:
- ref = self.pool.get(field._obj)._table
- cursor.execute("CREATE TABLE \"%s\" " \
- "(\"%s\" INTEGER NOT NULL REFERENCES \"%s\" " \
- "ON DELETE CASCADE, " \
- "\"%s\" INTEGER NOT NULL REFERENCES \"%s\" " \
- "ON DELETE CASCADE)" % \
- (field._rel, field._id1, self._table,
- field._id2, ref))
- cursor.execute("CREATE INDEX \"%s_%s_index\" " \
- "ON \"%s\" (\"%s\")" % \
- (field._rel, field._id1, field._rel,
- field._id1))
- cursor.execute("CREATE INDEX \"%s_%s_index\" " \
- "ON \"%s\" (\"%s\")" % \
- (field._rel, field._id2, field._rel,
- field._id2))
- else:
- cursor.execute("SELECT c.relname, a.attname, a.attlen, " \
- "a.atttypmod, a.attnotnull, a.atthasdef, " \
- "t.typname, " \
- "CASE WHEN a.attlen = -1 " \
- "THEN a.atttypmod-4 " \
- "ELSE a.attlen END as size " \
- "FROM pg_class c, pg_attribute a, pg_type t " \
- "WHERE c.relname = %s " \
- "AND a.attname = %s " \
- "AND c.oid = a.attrelid " \
- "AND a.atttypid = t.oid",
- (self._table, k))
- res = cursor.dictfetchall()
- if not res:
- if not isinstance(field, fields.Function):
- # add the missing field
- cursor.execute("ALTER TABLE \"%s\" " \
- "ADD COLUMN \"%s\" %s" % \
- (self._table, k, field.sql_type()[1]))
- if isinstance(field, (fields.Integer, fields.Float)):
- cursor.execute('ALTER TABLE "%s" ' \
- 'ALTER COLUMN "%s" SET DEFAULT 0' % \
- (self._table, k))
- # initialize it
- if not create and k in self._defaults:
- default = self._defaults[k](cursor, 1, {})
- if not default:
- cursor.execute("UPDATE \"%s\" " \
- "SET \"%s\" = NULL" % \
- (self._table, k))
- else:
- if isinstance(field, fields.Many2One) \
- and isinstance(default, (list, tuple)):
- default = default[0]
- cursor.execute("UPDATE \"%s\" " \
- "SET \"%s\" = '%s'" % \
- (self._table, k, default))
- # and add constraints if needed
- if isinstance(field, fields.Many2One):
- # res.user and res.group are not present when ir initialize
- if field._obj == 'res.user':
- ref = 'res_user'
- elif field._obj == 'res.group':
- ref = 'res_group'
- else:
- ref = self.pool.get(field._obj)._table
- cursor.execute("ALTER TABLE \"%s\" " \
- "ADD FOREIGN KEY (\"%s\") " \
- "REFERENCES \"%s\" " \
- "ON DELETE %s" % \
- (self._table, k, ref,
- field.ondelete))
- if field.select:
- cursor.execute("CREATE INDEX \"%s_%s_index\" " \
- "ON \"%s\" (\"%s\")" % \
- (self._table, k, self._table, k))
- if field.required:
- cursor.execute('SELECT id FROM "%s" ' \
- 'WHERE "%s" IS NULL' % \
- (self._table, k))
- if not cursor.rowcount:
- cursor.execute("ALTER TABLE \"%s\" " \
- "ALTER COLUMN \"%s\" " \
- "SET NOT NULL" % \
- (self._table, k))
- else:
- logger.notify_channel('init',
- LOG_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 ALTER COLUMN %s SET NOT NULL' % \
- (k, self._table, self._table, k))
- elif len(res)==1:
- f_pg_def = res[0]
- f_pg_type = f_pg_def['typname']
- f_pg_size = f_pg_def['size']
- f_pg_notnull = f_pg_def['attnotnull']
- if isinstance(field, fields.Function):
- logger.notify_channel('init', LOG_WARNING,
- 'column %s (%s) in table %s was converted '\
- 'to a function !\n' \
- 'You should remove this column from your database.' % \
- (k, field.string, self._table))
- f_obj_type = None
- else:
- f_obj_type = field.sql_type() \
- and field.sql_type()[0]
- if f_obj_type:
- if f_pg_type != f_obj_type:
- logger.notify_channel('init',
- LOG_WARNING,
- "column '%s' in table '%s' must " \
- "change type %s -> %s!" % \
- (k, self._table, f_pg_type, f_obj_type,))
- if f_pg_type == 'varchar' \
- and field._type == 'char' \
- and (f_pg_size != field.size \
- and not (f_pg_size == -5 and field.size is None)):
- # columns with the name 'type' cannot be changed
- # for an unknown reason?!
- if k != 'type':
- if field.size and f_pg_size > field.size:
- logger.notify_channel('init',
- LOG_WARNING,
- "column '%s' in table '%s' " \
- "has changed size (DB = %d, def = %d), strings will be truncated !" % \
- (k, self._table, f_pg_size, field.size))
-#TODO: check si y a des donnees qui vont poser probleme (select char_length())
-#TODO: issue a log message even if f_pg_size < field.size
- cursor.execute("ALTER TABLE \"%s\" " \
- "RENAME COLUMN \"%s\" " \
- "TO temp_change_size" % \
- (self._table,k))
- cursor.execute("ALTER TABLE \"%s\" " \
- "ADD COLUMN \"%s\" %s" % \
- (self._table, k, field.sql_type()[1]))
- cursor.execute("UPDATE \"%s\" " \
- "SET \"%s\" = temp_change_size::%s" % \
- (self._table, k, field.sql_type()[1]))
- cursor.execute("ALTER TABLE \"%s\" " \
- "DROP COLUMN temp_change_size" % \
- (self._table,))
- # if the field is required
- # and hasn't got a NOT NULL constraint
- if field.required and f_pg_notnull == 0:
- # set the field to the default value if any
- if self._defaults.has_key(k):
- default = self._defaults[k](cursor,
- 1, {})
- if default:
- if isinstance(field, fields.Many2One) \
- and isinstance(default, (list, tuple)):
- default = default[0]
- cursor.execute("UPDATE \"%s\" " \
- "SET \"%s\" = '%s' WHERE %s is NULL" % \
- (self._table, k, default, k))
- cursor.execute('SELECT "%s" FROM "%s" ' \
- 'WHERE "%s" IS NULL' % \
- (k, self._table, k))
- if not cursor.rowcount:
- # add the NOT NULL constraint
- cursor.execute("ALTER TABLE \"%s\" " \
- "ALTER COLUMN \"%s\" SET NOT NULL" % \
- (self._table, k))
- else:
- logger.notify_channel('init',
- LOG_WARNING,
- 'unable to set ' \
- 'a NOT NULL constraint on column %s of the %s table !\n' \
-'If you want to have it, you should update the records and execute manually:\n'\
- 'ALTER TABLE %s ALTER COLUMN %s SET NOT NULL' % \
- (k, self._table, self._table, k))
- elif not field.required and f_pg_notnull == 1:
- cursor.execute("ALTER TABLE \"%s\" " \
- "ALTER COLUMN \"%s\" DROP NOT NULL" % \
- (self._table, k))
- cursor.execute("SELECT indexname FROM pg_indexes " \
- "WHERE indexname = '%s_%s_index' AND tablename = '%s'" % \
- (self._table, k, self._table))
- res = cursor.dictfetchall()
- if not res and field.select:
- cursor.execute("CREATE INDEX \"%s_%s_index\" " \
- "ON \"%s\" (\"%s\")" % \
- (self._table, k, self._table, k))
- if res and not field.select:
- cursor.execute("DROP INDEX \"%s_%s_index\"" % \
- (self._table, k))
- if isinstance(field, fields.Many2One):
- # for some fields in ir are linked to res objects
- # that are not yet in the pooler
- if field._obj in ('res.user', 'res.group'):
- ref = field._obj.replace('.','_')
- else:
- ref = self.pool.get(field._obj)._table
- cursor.execute('SELECT confdeltype, conname ' \
- 'FROM pg_constraint as con, ' \
- 'pg_class as cl1, pg_class as cl2, ' \
- 'pg_attribute as att1, pg_attribute as att2 ' \
- 'WHERE con.conrelid = cl1.oid ' \
- 'AND cl1.relname = %s ' \
- 'AND con.confrelid = cl2.oid ' \
- 'AND cl2.relname = %s ' \
- 'AND array_lower(con.conkey, 1) = 1 ' \
- 'AND con.conkey[1] = att1.attnum ' \
- 'AND att1.attrelid = cl1.oid ' \
- 'AND att1.attname = %s ' \
- 'AND array_lower(con.confkey, 1) = 1 ' \
- 'AND con.confkey[1] = att2.attnum ' \
- 'AND att2.attrelid = cl2.oid ' \
- 'AND att2.attname = %s ' \
- 'AND con.contype = \'f\'',
- (self._table, ref, k, 'id'))
- res = cursor.dictfetchall()
- if res:
- confdeltype = {
- 'RESTRICT': 'r',
- 'NO ACTION': 'a',
- 'CASCADE': 'c',
- 'SET NULL': 'n',
- 'SET DEFAULT': 'd',
- }
- if res[0]['confdeltype'] != \
- confdeltype.get(
- field.ondelete.upper(),
- 'a'):
- cursor.execute('ALTER TABLE "' + \
- self._table + '" ' \
- 'DROP CONSTRAINT "' + \
- res[0]['conname'] + '"')
- cursor.execute('ALTER TABLE "' + \
- self._table + '" ' \
- 'ADD FOREIGN KEY ' \
- '("' + k + '") ' \
- 'REFERENCES "' + ref + \
- '" ON DELETE ' + field.ondelete)
+ if field_name in self._defaults:
+ default_fun = self._defaults[field_name]
+
+ def unpack_wrapper(fun):
+ def unpack_result(*a):
+ try: # XXX ugly hack
+ result = fun(*a)
+ except:
+ return None
+ clean_results = self.__clean_defaults(
+ {field_name: result})
+ return clean_results[field_name]
+ return unpack_result
+ default_fun = unpack_wrapper(default_fun)
+
+ print field.sql_type()
+
+ table.add_raw_column(
+ field_name, field.sql_type(), default_fun, field.size)
+
+ if isinstance(field, fields.Many2One):
+ if field._obj in ('res.user', 'res.group'):
+ ref = field._obj.replace('.','_')
else:
- # TODO add error message
- logger.notify_channel('init', LOG_ERROR, '')
- else:
- cursor.execute("SELECT relname FROM pg_class " \
- "WHERE relkind in ('r', 'v') AND relname = %s",
- (self._table,))
- create = not bool(cursor.fetchone())
+ ref = self.pool.get(field._obj)._table
+ table.add_fk(field_name, ref, field.ondelete)
- for (key, con, msg) in self._sql_constraints:
- cursor.execute("SELECT conname FROM pg_constraint " \
- "WHERE conname = %s", ((self._table + '_' + key),))
- if not cursor.dictfetchall():
- try:
- cursor.execute('ALTER TABLE \"%s\" ' \
- 'ADD CONSTRAINT \"%s_%s\" %s' % \
- (self._table, self._table, key, con,))
- except:
- logger.notify_channel('init', LOG_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 %s' % \
- (con, self._table, self._table, self._table,key, con,))
+ if not isinstance(field, fields.Many2One):
+ # Postgres does not allow index on fk
+ table.index_action(
+ field_name, action=field.select and 'add' or 'remove')
+ table.not_null_action(
+ field_name, action=field.required and 'add' or 'remove')
- if create:
- if hasattr(self, "_sql"):
- for line in self._sql.split(';'):
- line2 = line.replace('\n', '').strip()
- if line2:
- cursor.execute(line2)
+ elif isinstance(field, fields.Many2Many):
+ if field._obj in ('res.user', 'res.group'):
+ ref = field._obj.replace('.','_')
+ else:
+ ref = self.pool.get(field._obj)._table
+ table.add_m2m(field_name, ref, field._rel, field._id1, field._id2)
- for k in self._columns:
- field = self._columns[k]
+ elif not isinstance(field, (fields.One2Many, fields.Function)):
+ raise Exception('Unknow field type !')
+
+
+ if "type has changed":
+ pass #try_to_migrate
+
+
+ for field_name, field in self._columns.iteritems():
if isinstance(field, fields.Many2One) \
and field._obj == self._name \
and field.left and field.right:
@@ -758,7 +486,12 @@ class ORM(object):
(self._table, field.left, field.right,
field.left, field.right))
if cursor.rowcount:
- self._rebuild_tree(cursor, 0, k, False, 0)
+ self._rebuild_tree(cursor, 0, field, False, 0)
+
+
+ for ident, constraint, msg in self._sql_constraints:
+ table.add_constraint( ident, constraint)
+
def __init__(self):
self._rpc_allowed = [
@@ -1732,6 +1465,8 @@ class ORM(object):
vals[field].append(('create', vals2))
elif fld_def._type in ('many2many',):
vals[field] = [('set', defaults[field])]
+ elif fld_def._type in ('boolean',):
+ vals[field] = bool(defaults[field])
else:
vals[field] = defaults[field]
return vals
@@ -2154,11 +1889,11 @@ class ORM(object):
while test:
if view_id:
where = (model and (" and model='%s'" % (self._name,))) or ''
- cursor.execute('SELECT arch, name, field_childs, id, type, ' \
+ cursor.execute('SELECT arch, field_childs, id, type, ' \
'inherit ' \
'FROM ir_ui_view WHERE id = %s ' + where, (view_id,))
else:
- cursor.execute('SELECT arch, name, field_childs, id, type, ' \
+ cursor.execute('SELECT arch, field_childs, id, type, ' \
'inherit ' \
'FROM ir_ui_view ' \
'WHERE model = %s AND type = %s AND inherit IS NULL ' \
@@ -2167,14 +1902,14 @@ class ORM(object):
sql_res = cursor.fetchone()
if not sql_res:
break
- test = sql_res[5]
+ test = sql_res[4]
view_id = test or sql_res[3]
model = False
# if a view was found
if sql_res:
- result['type'] = sql_res[4]
- result['view_id'] = sql_res[3]
+ result['type'] = sql_res[3]
+ result['view_id'] = sql_res[2]
result['arch'] = sql_res[0]
def _inherit_apply_rec(result, inherit_id):
@@ -2188,9 +1923,7 @@ class ORM(object):
result = _inherit_apply_rec(result, view_id)
return result
- result['arch'] = _inherit_apply_rec(result['arch'], sql_res[3])
-
- result['name'] = sql_res[1]
+ result['arch'] = _inherit_apply_rec(result['arch'], sql_res[2])
result['field_childs'] = sql_res[2] or False
# otherwise, build some kind of default view
else:
@@ -2224,7 +1957,6 @@ class ORM(object):
xml = ''
result['type'] = view_type
result['arch'] = xml
- result['name'] = 'default'
result['field_childs'] = False
result['view_id'] = 0
diff --git a/trytond/sql_db.py b/trytond/sql_db.py
index e8ee5cfa73..6ff8be71b1 100644
--- a/trytond/sql_db.py
+++ b/trytond/sql_db.py
@@ -8,6 +8,7 @@ from mx import DateTime as mdt
import zipfile
import version
from config import CONFIG
+from netsvc import Logger, LOG_ERROR, LOG_WARNING
RE_FROM = re.compile('.* from "?([a-zA-Z_0-9]+)"?.*$')
RE_INTO = re.compile('.* into "?([a-zA-Z_0-9]+)"?.*$')
@@ -224,3 +225,259 @@ def init_db(cursor):
(module_id, dependency))
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+
+
+
+def table_exist(cursor, table_name):
+ cursor.execute("SELECT relname FROM pg_class " \
+ "WHERE relkind = 'r' AND relname = %s",
+ (table_name,))
+ return bool(cursor.rowcount)
+
+
+class table_handler:
+
+ def __init__(self, cursor, table_name, object_name=None, module_name=None):
+ self.table_name = table_name
+ self.table = {}
+ self.constraint = []
+ self.fk_deltype = {}
+ self.index = {}
+ self.field2module = {}
+ self.module_name = module_name
+ self.cursor = cursor
+
+ # Create new table if necessary
+ if not table_exist(self.cursor, self.table_name):
+ self.cursor.execute("CREATE TABLE %s " \
+ "(id SERIAL NOT NULL, " \
+ "PRIMARY KEY(id))"% self.table_name)
+
+ # Fetch columns definitions from the table
+ self.cursor.execute("SELECT at.attname, at.attlen, "\
+ "at.atttypmod, at.attnotnull, at.atthasdef, "\
+ "ty.typname, "\
+ "CASE WHEN at.attlen = -1 "\
+ "THEN at.atttypmod-4 "\
+ "ELSE at.attlen END as size "\
+ "FROM pg_class cl "\
+ "JOIN pg_attribute at on (cl.oid = at.attrelid) "\
+ "JOIN pg_type ty on (at.atttypid = ty.oid) "\
+ "WHERE cl.relname = %s AND at.attnum > 0",
+ (table_name,))
+ for line in self.cursor.fetchall():
+ column, length, typmod, notnull, hasdef, typname, size = line
+ self.table[column] = {
+ "length": length,
+ "typmod": typmod,
+ "notnull": notnull ,
+ "hasdef": hasdef,
+ "size": size,
+ "typname": typname}
+
+ # fetch constrains for the table
+ self.cursor.execute("SELECT co.contype, co.confdeltype, at.attname, "\
+ "cl2.relname, co.conname "\
+ "FROM pg_constraint co "\
+ "LEFT JOIN pg_class cl on (co.conrelid = cl.oid) "\
+ "LEFT JOIN pg_class cl2 on (co.confrelid = cl2.oid) "\
+ "LEFT JOIN pg_attribute at on (co.conkey[1] = at.attnum) "\
+ "WHERE cl.relname = %s",
+ (table_name,))
+ for line in self.cursor.fetchall():
+ contype, confdeltype, column, ref, conname = line
+ if contype == 'f':
+ self.fk_deltype[column] = confdeltype
+ else:
+ if conname not in self.constraint:
+ self.constraint.append(conname)
+
+ # Fetch indexes defined for the table
+ self.cursor.execute("SELECT cl2.relname "\
+ "FROM pg_index ind "\
+ "JOIN pg_class cl on (cl.oid = ind.indrelid) "\
+ "JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) "\
+ "WHERE cl.relname = %s",
+ (table_name,))
+ self.index = [l[0] for l in self.cursor.fetchall()]
+
+ # Keep track of which module created each field
+ if 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',
+ (object_name,)
+ )
+ for line in self.cursor.fetchall():
+ self.field2module[line[0]] = line[1]
+
+ def migrate_column(self, column_name, column_type):
+
+ self.cursor.execute("ALTER TABLE \"%s\" " \
+ "RENAME COLUMN \"%s\" " \
+ "TO temp_change_size" % \
+ (self.table_name, column_name))
+ self.cursor.execute("ALTER TABLE \"%s\" " \
+ "ADD COLUMN \"%s\" %s" % \
+ (self.table_name, column_name, column_type))
+ self.cursor.execute("UPDATE \"%s\" " \
+ "SET \"%s\" = temp_change_size::%s" % \
+ (self.table_name, column_name, column_type))
+ self.cursor.execute("ALTER TABLE \"%s\" " \
+ "DROP COLUMN temp_change_size" % \
+ (self.table_name,))
+
+
+ def add_raw_column(self, column_name, column_type, default_fun=None,
+ field_size=None, migrate = True):
+ if column_name in self.table:
+
+ #print ">>>",self.table_name, column_name, self.table[column_name]['typname'], self.table[column_name]['size'], column_type, field_size, "\n\n"
+
+ if not migrate:
+ return
+ base_type = column_type[0].lower()
+ if not base_type == self.table[column_name]['typname']:
+ raise Exception(
+ 'Unable to migrate column % s from %s to %s.' % \
+ (column_name, self.table[column_name]['typname'], base_type))
+ if not base_type == 'varchar':
+ return
+ if field_size == None:
+ if self.table[column_name]['size'] > 0:
+ self.migrate_column(column_name, base_type)
+ return
+ elif self.table[column_name]['size'] == field_size:
+ return
+ elif self.table[column_name]['size'] > 0 and \
+ self.table[column_name]['size'] < field_size:
+ self.migrate_column(column_name, base_type)
+ else:
+ raise Exception(
+ 'Unable to migrate column %s from varchar(%s) to varchar(%s).' % \
+ (column_name, self.table[column_name]['size'], field_size))
+ column_type = column_type[1]
+ if column_name == 'operand': print column_type
+ self.cursor.execute("ALTER TABLE %s ADD COLUMN \"%s\" %s"%
+ (self.table_name, column_name, column_type))
+ if default_fun is not None:
+ default = default_fun(self.cursor, 0, {})
+ if default: # XXX What about a boolean who's default false
+ # or an default integer of 0 .. all default
+ # fun should return None instead of false when
+ # there is no default to put. (see action.py l.230)
+ self.cursor.execute("UPDATE " + self.table_name + " "\
+ "SET \"" + column_name + "\" = %s",
+ (default,))
+
+ def add_m2m(self, column_name, other_table, relation_table, rtable_from, rtable_to):
+ if not table_exist(self.cursor, other_table):
+ raise Exception("table %s not found"%other_table)
+ rtable = table_handler(
+ self.cursor, relation_table, object_name=None,
+ module_name= self.module_name)
+ rtable.add_raw_column(rtable_from, ('int4', 'int4'))
+ rtable.add_raw_column(rtable_to, ('int4', 'int4'))
+ rtable.add_fk(rtable_from, self.table_name)
+ rtable.add_fk(rtable_to, other_table)
+ rtable.index_action(rtable_from, 'add')
+ rtable.index_action(rtable_to, 'add')
+
+ def add_fk(self, column_name, reference, on_delete=None):
+ on_delete_code = {
+ 'RESTRICT': 'r',
+ 'NO ACTION': 'a',
+ 'CASCADE': 'c',
+ 'SET NULL': 'n',
+ 'SET DEFAULT': 'd',
+ }
+ if on_delete is not None :
+ on_delete = on_delete.upper()
+ if on_delete not in on_delete_code:
+ raise Exception('On delete action not supported !')
+ else:
+ on_delete = 'SET NULL'
+ code = on_delete_code[on_delete]
+
+ if self.fk_deltype.get(column_name) == code:
+ # The fk exist and the delete action is ok
+ return
+ self.cursor.execute("ALTER TABLE " + self.table_name + " " \
+ "ADD FOREIGN KEY (\"" + column_name + "\") " \
+ "REFERENCES " + reference + " " \
+ "ON DELETE " + on_delete)
+
+ def index_action(self, column_name, action='add'):
+ index_name = "%s_%s_index" % (self.table_name, column_name)
+
+ if action == 'add':
+ if index_name in self.index:
+ return
+ self.cursor.execute("CREATE INDEX " + index_name + " " \
+ "ON " + self.table_name + " (" + column_name + ")")
+ elif action == 'remove':
+ if self.field2module.get(column_name) != self.module_name:
+ return
+
+ self.cursor.execute("SELECT * FROM pg_indexes " \
+ "WHERE indexname = '%s'" %
+ (index_name,))
+ if self.cursor.rowcount:
+ self.cursor.execute("DROP INDEX %s " % (index_name,))
+
+
+ def not_null_action(self, column_name, action='add'):
+ if column_name not in self.table:
+ return
+
+ if action == 'add':
+ if self.table[column_name]['notnull']:
+ 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 " + self.table_name + " " \
+ "ALTER COLUMN \"" + column_name +"\" " \
+ "SET NOT NULL")
+ else:
+ logger = Logger()
+ logger.notify_channel(
+ 'init',
+ LOG_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 ALTER COLUMN %s SET NOT NULL' % \
+ (column_name, self.table_name, self.table_name, column_name))
+ elif action == 'remove':
+ if not self.table[column_name]['notnull']:
+ return
+ if self.field2module.get(column_name) != self.module_name:
+ return
+ self.cursor.execute("ALTER TABLE %s " \
+ "ALTER COLUMN %s DROP NOT NULL"%
+ (self.table_name, column_name))
+
+ def add_constraint(self, ident, constraint):
+ ident = self.table_name + "_" + ident
+ if ident in self.constraint:
+ # This constrain already exist
+ return
+ try:
+ self.cursor.execute('ALTER TABLE \"%s\" ' \
+ 'ADD CONSTRAINT \"%s\" %s' % \
+ (self.table_name, ident, constraint,))
+ except:
+ raise
+ logger = Logger()
+ logger.notify_channel(
+ 'init', LOG_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,))