Files
tryton/trytond/model/modelsql.py
T
Cédric Krier df1fd283ac New transaction management
Remove cursor, user and context from methods and use Transaction instead
2010-08-19 22:01:12 +02:00

2178 lines
99 KiB
Python

#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 __future__ import with_statement
import contextlib
import datetime
import re
from trytond.model import ModelStorage
from trytond.model import fields
from trytond.backend import FIELDS, TableHandler
from trytond.backend import DatabaseIntegrityError, Database
from trytond.tools import reduce_ids
from trytond.const import OPERATORS
from trytond.transaction import Transaction
_RE_UNIQUE = re.compile('UNIQUE\s*\((.*)\)', re.I)
_RE_CHECK = re.compile('CHECK\s*\((.*)\)', re.I)
class ModelSQL(ModelStorage):
"""
Define a model with storage in database.
"""
_table = None # The name of the table in database
_order = None
_order_name = None # Use to force order field when sorting on Many2One
_history = False
def __init__(self):
super(ModelSQL, self).__init__()
self._sql_constraints = []
self._order = [('id', 'ASC')]
self._sql_error_messages = {}
if not self._table:
self._table = self._name.replace('.', '_')
assert self._table[-9:] != '__history', \
'Model _table %s cannot end with "__history"' % self._table
def init(self, module_name):
super(ModelSQL, self).init(module_name)
if self.table_query():
return
# create/update table in the database
table = TableHandler(Transaction().cursor, self, module_name)
if self._history:
history_table = TableHandler(Transaction().cursor, self,
module_name, history=True)
timestamp_field = FIELDS['timestamp']
integer_field = FIELDS['integer']
logs = (
('create_date', timestamp_field.sql_type(None),
timestamp_field.sql_format, lambda *a: datetime.datetime.now(),
self.create_date.string),
('write_date', timestamp_field.sql_type(None),
timestamp_field.sql_format, None, self.write_date.string),
('create_uid', (integer_field.sql_type(None)[0],
'INTEGER REFERENCES res_user ON DELETE SET NULL',),
integer_field.sql_format, lambda *a: 0, self.create_uid.string),
('write_uid', (integer_field.sql_type(None)[0],
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
integer_field.sql_format, None, self.write_uid.string),
)
for log in logs:
table.add_raw_column(log[0], log[1], log[2],
default_fun=log[3], migrate=False, string=log[4])
if self._history:
history_logs = (
('create_date', timestamp_field.sql_type(None),
timestamp_field.sql_format, self.create_date.string),
('write_date', timestamp_field.sql_type(None),
timestamp_field.sql_format, self.write_date.string),
('create_uid', (integer_field.sql_type(None)[0],
'INTEGER REFERENCES res_user ON DELETE SET NULL',),
integer_field.sql_format, self.create_uid.string),
('write_uid', (integer_field.sql_type(None)[0],
'INTEGER REFERENCES res_user ON DELETE SET NULL'),
integer_field.sql_format, self.write_uid.string),
)
for log in history_logs:
history_table.add_raw_column(log[0], log[1], log[2],
migrate=False, string=log[3])
history_table.index_action('id', action='add')
for field_name, field in self._columns.iteritems():
default_fun = None
if field_name in (
'id',
'write_uid',
'write_date',
'create_uid',
'create_date',
'rec_name',
):
continue
if not hasattr(field, 'set'):
if field_name in self._defaults:
default_fun = self._defaults[field_name]
def unpack_wrapper(fun):
def unpack_result(*a):
try: # XXX ugly hack: some default fct try
# to access the non-existing table
result = fun(*a)
except Exception:
return None
clean_results = self._clean_defaults(
{field_name: result})
return clean_results[field_name]
return unpack_result
default_fun = unpack_wrapper(default_fun)
table.add_raw_column(field_name,
FIELDS[field._type].sql_type(field),
FIELDS[field._type].sql_format, default_fun,
hasattr(field, 'size') and field.size or None,
string=field.string)
if self._history:
history_table.add_raw_column(field_name,
FIELDS[field._type].sql_type(field), None,
string=field.string)
if isinstance(field, (fields.Integer, fields.Float)):
table.db_default(field_name, 0)
if isinstance(field, (fields.Boolean)):
table.db_default(field_name, False)
if isinstance(field, fields.Many2One):
if field.model_name in ('res.user', 'res.group'):
ref = field.model_name.replace('.','_')
else:
ref = self.pool.get(field.model_name)._table
table.add_fk(field_name, ref, field.ondelete)
table.index_action(
field_name, action=field.select and 'add' or 'remove')
required = field.required
if isinstance(field, (fields.Integer, fields.Float,
fields.Boolean)):
required = True
table.not_null_action(
field_name, action=required and 'add' or 'remove')
elif not isinstance(field, (fields.One2Many, fields.Function,
fields.Many2Many)):
raise Exception('Unknow field type !')
for field_name, field in self._columns.iteritems():
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
with Transaction().set_user(0):
self._rebuild_tree(field_name, False, 0)
for ident, constraint, _ in self._sql_constraints:
table.add_constraint(ident, constraint)
if self._history:
self._update_history_table()
cursor = Transaction().cursor
cursor.execute('SELECT id FROM "' + self._table + '"')
if cursor.fetchone():
cursor.execute('SELECT id FROM "' + self._table + '__history"')
if not cursor.fetchone():
columns = ['"' + str(x) + '"' for x in self._columns
if not hasattr(self._columns[x], 'set')]
cursor.execute('INSERT ' \
'INTO "' + self._table + '__history" '\
'(' + ','.join(columns) + ') ' \
'SELECT ' + ','.join(columns) + \
' FROM "' + self._table + '"')
cursor.execute('UPDATE "' + self._table + '__history" ' \
'SET write_date = NULL')
def _update_history_table(self):
if self._history:
table = TableHandler(Transaction().cursor, self)
history_table = TableHandler(Transaction().cursor, self,
history=True)
for column_name in table._columns:
string = ''
if column_name in self._columns:
string = self._columns[column_name].string
history_table.add_raw_column(column_name,
(table._columns[column_name]['typname'],
table._columns[column_name]['typname']),
None, string=string)
def _get_error_messages(self):
res = super(ModelSQL, self)._get_error_messages()
res += self._sql_error_messages.values()
for _, _, error in self._sql_constraints:
res.append(error)
return res
def default_sequence(self):
'''
Return the default value for sequence field.
'''
table = self._table
if 'sequence' not in self._columns:
for model in self._inherits:
model_obj = self.pool.get(model)
if 'sequence' in model_obj._columns:
table = model_obj._table
break
cursor = Transaction().cursor
cursor.execute('SELECT MAX(sequence) ' \
'FROM "' + table + '"')
res = cursor.fetchone()
if res:
return res[0]
return 0
def table_query(self):
'''
Return None if the model is a real table in the database
or return a tuple with the SQL query and the arguments.
:return: None or a tuple with a SQL query and arguments
'''
return None
def create(self, values):
super(ModelSQL, self).create(values)
cursor = Transaction().cursor
if self.table_query():
return False
values = values.copy()
# Clean values
for key in ('create_uid', 'create_date', 'write_uid', 'write_date',
'id'):
if key in values:
del values[key]
# Get default values
default = []
avoid_table = []
for (i, j) in self._inherits.items():
if j in values:
avoid_table.append(i)
for i in self._columns.keys(): # + self._inherit_fields.keys():
if not i in values \
and i not in ('create_uid', 'create_date',
'write_uid', 'write_date'):
default.append(i)
for i in self._inherit_fields.keys():
if (not i in values) \
and (not self._inherit_fields[i][0] in avoid_table):
default.append(i)
if len(default):
defaults = self.default_get(default, with_rec_name=False)
for field in defaults.keys():
if '.' in field:
del defaults[field]
if field in ('create_uid', 'create_date',
'write_uid', 'write_date'):
del defaults[field]
values.update(self._clean_defaults(defaults))
(upd0, upd1, upd2) = ('', '', [])
upd_todo = []
# Create inherits
tocreate = {}
for i in self._inherits:
if self._inherits[i] not in values:
tocreate[i] = {}
for i in values.keys():
if i in self._inherit_fields:
(inherits, _, _) = self._inherit_fields[i]
if i in self._columns:
continue
if inherits in tocreate:
tocreate[inherits][i] = values[i]
if i not in self._columns:
del values[i]
for inherits in tocreate:
inherits_obj = self.pool.get(inherits)
inherits_id = inherits_obj.create(tocreate[inherits])
values[self._inherits[inherits]] = inherits_id
# Insert record
for field in values:
if not hasattr(self._columns[field], 'set'):
upd0 = upd0 + ',"' + field + '"'
upd1 = upd1 + ', %s'
upd2.append(FIELDS[self._columns[field]._type].sql_format(
values[field]))
else:
upd_todo.append(field)
if field in self._columns \
and hasattr(self._columns[field], 'selection') \
and self._columns[field].selection \
and values[field]:
if self._columns[field]._type == 'reference':
val = values[field].split(',')[0]
else:
val = values[field]
if isinstance(self._columns[field].selection, (tuple, list)):
if val not in dict(self._columns[field].selection):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
else:
if val not in dict(getattr(self,
self._columns[field].selection)()):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
upd0 += ', create_uid, create_date'
upd1 += ', %s, %s'
upd2.append(Transaction().user)
upd2.append(datetime.datetime.now())
try:
id_new = cursor.nextid(self._table)
if id_new:
cursor.execute('INSERT INTO "' + self._table + '" ' \
'(id' + upd0 + ') ' \
'VALUES (' + str(id_new) + upd1 + ')', tuple(upd2))
else:
cursor.execute('INSERT INTO "' + self._table + '" ' \
'(' + upd0[1:] + ') ' \
'VALUES (' + upd1[1:] + ')', tuple(upd2))
id_new = cursor.lastid()
except DatabaseIntegrityError, exception:
with contextlib.nested(Transaction().new_cursor(),
Transaction().set_user(0)):
for field_name in self._columns:
field = self._columns[field_name]
# Check required fields
if field.required and \
not hasattr(field, 'set') and \
not isinstance(field, (fields.Integer,
fields.Float)) and \
field_name not in ('create_uid', 'create_date'):
if not values.get(field_name):
self.raise_user_error('required_field',
error_args=self._get_error_args(
field_name))
if isinstance(field, fields.Many2One) \
and values.get(field_name):
model_obj = self.pool.get(field.model_name)
create_records = Transaction().create_records.get(
field.model_name, set())
delete_records = Transaction().delete_records.get(
field.model_name, set())
if not ((model_obj.search([
('id', '=', values[field_name]),
], order=[])
or values[field_name] in create_records)
and values[field_name] not in delete_records):
self.raise_user_error('foreign_model_missing',
error_args=self._get_error_args(
field_name))
for name, _, error in self._sql_constraints:
if name in exception[0]:
self.raise_user_error(error)
for name, error in self._sql_error_messages:
if name in exception[0]:
self.raise_user_error(error)
raise
domain1, domain2 = self.pool.get('ir.rule').domain_get(self._name,
mode='create')
if domain1:
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE id = %s AND (' + domain1 + ')',
[id_new] + domain2)
if not cursor.fetchone():
self.raise_user_error('access_error', self._description)
Transaction().create_records.setdefault(self._name, set()).add(id_new)
upd_todo.sort(lambda x, y: self._columns[x].priority - \
self._columns[y].priority)
for field in upd_todo:
self._columns[field].set([id_new], self, field, values[field])
if self._history:
cursor.execute('INSERT INTO "' + self._table + '__history" ' \
'(id' + upd0 + ') ' \
'SELECT id' + upd0 + ' ' \
'FROM "' + self._table + '" ' \
'WHERE id = %s',(id_new,))
self._validate([id_new])
# Check for Modified Preorder Tree Traversal
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
self._update_tree(id_new, k, field.left, field.right)
self.trigger_create(id_new)
return id_new
def read(self, ids, fields_names=None):
rule_obj = self.pool.get('ir.rule')
translation_obj = self.pool.get('ir.translation')
super(ModelSQL, self).read(ids, fields_names=fields_names)
cursor = Transaction().cursor
if not fields_names:
fields_names = list(set(self._columns.keys() \
+ self._inherit_fields.keys()))
int_id = False
if isinstance(ids, (int, long)):
int_id = True
ids = [ids]
if not ids:
return []
# construct a clause for the rules :
domain1, domain2 = rule_obj.domain_get(self._name, mode='read')
fields_related = {}
datetime_fields = []
for field_name in fields_names:
if field_name == '_timestamp':
continue
if '.' in field_name:
field, field_related = field_name.split('.', 1)
fields_related.setdefault(field, [])
fields_related[field].append(field_related)
elif field_name in self._columns:
field = self._columns[field_name]
else:
field = self._inherit_fields[field_name][2]
if hasattr(field, 'datetime_field') and field.datetime_field:
datetime_fields.append(field.datetime_field)
# all inherited fields + all non inherited fields
fields_pre = [x for x in \
fields_names + fields_related.keys() + datetime_fields \
if (x in self._columns and \
not hasattr(self._columns[x], 'set')) or \
(x == '_timestamp')] + \
self._inherits.values()
res = []
table_query = ''
table_args = []
if self.table_query():
table_query, table_args = self.table_query()
table_query = '(' + table_query + ') AS '
in_max = cursor.IN_MAX
history_order = ''
history_clause = ''
history_limit = ''
history_args = []
if (self._history
and Transaction().context.get('_datetime')
and not table_query):
in_max = 1
table_query = '"' + self._table + '__history" AS '
history_clause = ' AND (COALESCE(write_date, create_date) <= %s)'
history_order = ' ORDER BY COALESCE(write_date, create_date) DESC'
history_limit = cursor.limit_clause('', 1)
history_args = [Transaction().context['_datetime']]
if len(fields_pre) :
fields_pre2 = ['"%s"."%s" AS "%s"' % (self._table, x, x)
for x in fields_pre if x != '_timestamp']
if '_timestamp' in fields_pre:
if not self.table_query():
fields_pre2 += ['CAST(EXTRACT(EPOCH FROM '
'(COALESCE("%s".write_date, "%s".create_date))) AS %s'
') AS _timestamp' %
(self._table, self._table,
FIELDS['numeric'].sql_type(self.create_date)[1])]
for i in range(0, len(ids), in_max):
sub_ids = ids[i:i + in_max]
red_sql, red_ids = reduce_ids('id', sub_ids)
if domain1:
cursor.execute('SELECT ' + \
','.join(fields_pre2 +
['"%s".id AS id' % self._table]) + \
' FROM ' + table_query + '\"' + self._table +'\" ' \
'WHERE ' + red_sql + \
history_clause + \
' AND (' + domain1 + ') ' + history_order + \
history_limit,
table_args + red_ids + history_args + domain2)
else:
cursor.execute('SELECT ' + \
','.join(fields_pre2 +
['"%s".id AS id' % self._table]) + \
' FROM ' + table_query + '\"' + self._table + '\" '\
'WHERE ' + red_sql + \
history_clause + history_order + history_limit,
table_args + red_ids + history_args)
dictfetchall = cursor.dictfetchall()
if not len(dictfetchall) == len({}.fromkeys(sub_ids)):
if domain1:
cursor.execute('SELECT id FROM ' + \
table_query + '\"' + self._table + '\" ' \
'WHERE ' + red_sql + \
history_clause + history_order + history_limit,
table_args + red_ids + history_args)
rowcount = cursor.rowcount
if rowcount == -1 or rowcount is None:
rowcount = len(cursor.fetchall())
if rowcount == len({}.fromkeys(sub_ids)):
self.raise_user_error('access_error',
self._description)
self.raise_user_error('read_error', self._description)
res.extend(dictfetchall)
else:
res = [{'id': x} for x in ids]
for field in fields_pre:
if field == '_timestamp':
continue
if getattr(self._columns[field], 'translate', False):
ids = [x['id'] for x in res]
res_trans = translation_obj._get_ids( self._name + ',' + field,
'model', Transaction().language, ids)
for i in res:
i[field] = res_trans.get(i['id'], False) or i[field]
for table in self._inherits:
field = self._inherits[table]
inherits_fields = list(
set(self._inherit_fields.keys()).intersection(
set(fields_names + fields_related.keys() + datetime_fields)
).difference(set(self._columns.keys())))
if not inherits_fields:
for record in res:
if field not in fields_names + fields_related.keys() + \
datetime_fields:
del record[field]
continue
inherit_related_fields = []
for inherit_field in inherits_fields:
if inherit_field in fields_related:
for field_related in fields_related[inherit_field]:
inherit_related_fields.append(
inherit_field + '.' + field_related)
res2 = self.pool.get(table).read([x[field] for x in res],
fields_names=inherits_fields + inherit_related_fields)
res3 = {}
for i in res2:
res3[i['id']] = i
del i['id']
for record in res:
record.update(res3[record[field]])
if field not in \
fields_names + fields_related.keys() + datetime_fields:
del record[field]
ids = [x['id'] for x in res]
# all non inherited fields for which there is a get attribute
fields_post = [x for x in \
fields_names + fields_related.keys() + datetime_fields \
if x in self._columns \
and hasattr(self._columns[x], 'get')]
func_fields = {}
for field in fields_post:
if isinstance(self._columns[field], fields.Function):
key = (self._columns[field].getter,
getattr(self._columns[field], 'datetime_field', None))
func_fields.setdefault(key, [])
func_fields[key].append(field)
continue
if hasattr(self._columns[field], 'datetime_field') \
and self._columns[field].datetime_field:
for record in res:
with Transaction().set_context(_datetime=
record[self._columns[field].datetime_field]):
res2 = self._columns[field].get( [record['id']], self,
field, values=[record])
record[field] = res2[record['id']]
continue
# get the value of that field for all records/ids
res2 = self._columns[field].get(ids, self, field, values=res)
for record in res:
record[field] = res2[record['id']]
for key in func_fields:
field_list = func_fields[key]
field = field_list[0]
_, datetime_field = key
if datetime_field:
for record in res:
with Transaction().set_context(
_datetime=record[datetime_field]):
res2 = self._columns[field].get([record['id']], self,
field_list, values=[record])
for field in res2:
record[field] = res2[field][record['id']]
continue
res2 = self._columns[field].get(ids, self, field_list, values=res)
for field in res2:
for record in res:
record[field] = res2[field][record['id']]
to_del = []
fields_related2values = {}
for field in fields_related.keys() + datetime_fields:
if field not in fields_names:
to_del.append(field)
if field not in self._columns:
continue
if field not in fields_related.keys():
continue
fields_related2values.setdefault(field, {})
if self._columns[field]._type == 'many2one':
obj = self.pool.get(self._columns[field].model_name)
if hasattr(self._columns[field], 'datetime_field') \
and self._columns[field].datetime_field:
for record in res:
if not record[field]:
continue
with Transaction().set_context(_datetime=
record[self._columns[field].datetime_field]):
record2 = obj.read(record[field],
fields_related[field])
record_id = record2['id']
del record2['id']
fields_related2values[field].setdefault(record_id, {})
fields_related2values[
field][record_id][record['id']] = record2
else:
for record in obj.read([x[field] for x in res
if x[field]], fields_related[field]):
record_id = record['id']
del record['id']
fields_related2values[field].setdefault(record_id, {})
for record2 in res:
fields_related2values[field][record_id]\
[record2['id']] = record
elif self._columns[field]._type == 'reference':
for record in res:
if not record[field]:
continue
model_name, record_id = record[field].split(',', 1)
if not model_name:
continue
record_id = int(record_id)
if not record_id:
continue
obj = self.pool.get(model_name)
record2 = obj.read(record_id, fields_related[field])
del record2['id']
fields_related2values[field][record_id] = record2
if to_del or fields_related.keys() or datetime_fields:
for record in res:
for field in fields_related.keys():
if field not in self._columns:
continue
for related in fields_related[field]:
if self._columns[field]._type == 'many2one':
if record[field]:
record[field + '.' + related] = \
fields_related2values[field]\
[record[field]][record['id']][related]
else:
record[field + '.' + related] = False
elif self._columns[field]._type == 'reference':
if record[field]:
model_name, record_id = record[field].split(
',', 1)
if not model_name:
continue
record_id = int(record_id)
if not record_id:
continue
record[field + '.' + related] = \
fields_related2values[field][
record_id][related]
for field in to_del:
del record[field]
if int_id:
return res[0]
return res
def write(self, ids, values):
cursor = Transaction().cursor
# Call before cursor cache cleaning
trigger_eligibles = self.trigger_write_get_eligibles(
isinstance(ids, (int, long)) and [ids] or ids)
super(ModelSQL, self).write(ids, values)
if not ids:
return True
if self.table_query():
return True
values = values.copy()
if isinstance(ids, (int, long)):
ids = [ids]
else:
# _update_tree works if only one record has changed
update_tree = False
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
update_tree = True
if update_tree:
for object_id in ids:
self.write(object_id, values)
return True
if Transaction().timestamp:
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
clause = ('(id = %s AND '
'CAST(EXTRACT(EPOCH FROM '
'COALESCE(write_date, create_date)) AS ' + \
FIELDS['numeric'].sql_type(self.create_date)[1] + \
') > %s)')
args = []
for i in sub_ids:
if Transaction().timestamp.get(self._name + ',' + str(i)):
args.append(i)
args.append(Transaction().timestamp[
self._name + ',' +str(i)])
if args:
cursor.execute("SELECT id " \
'FROM "' + self._table + '" ' \
'WHERE ' + ' OR '.join(
(clause,) * (len(args)/2)), args)
if cursor.fetchone():
raise Exception('ConcurrencyException',
'Records were modified in the meanwhile')
for i in ids:
if Transaction().timestamp.get(self._name + ',' + str(i)):
del Transaction().timestamp[self._name + ',' +str(i)]
# Clean values
for key in ('create_uid', 'create_date', 'write_uid', 'write_date',
'id'):
if key in values:
del values[key]
upd0 = []
upd1 = []
upd_todo = []
updend = []
direct = []
for field in values:
if field in self._columns:
if not hasattr(self._columns[field], 'set'):
if ((not getattr(self._columns[field], 'translate', False))
or (Transaction().language == 'en_US')):
upd0.append(('"' + field + '"', '%s'))
upd1.append(FIELDS[self._columns[field]._type]\
.sql_format(values[field]))
direct.append(field)
else:
upd_todo.append(field)
else:
updend.append(field)
if field in self._columns \
and hasattr(self._columns[field], 'selection') \
and self._columns[field].selection \
and values[field]:
if self._columns[field]._type == 'reference':
val = values[field].split(',')[0]
else:
val = values[field]
if isinstance(self._columns[field].selection, (tuple, list)):
if val not in dict(self._columns[field].selection):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
else:
if val not in dict(getattr(self,
self._columns[field].selection)()):
raise Exception('ValidateError',
'The value "%s" for the field "%s" ' \
'is not in the selection' % \
(val, field))
upd0.append(('write_uid', '%s'))
upd0.append(('write_date', '%s'))
upd1.append(Transaction().user)
upd1.append(datetime.datetime.now())
domain1, domain2 = self.pool.get('ir.rule').domain_get(self._name,
mode='write')
if domain1:
domain1 = ' AND (' + domain1 + ') '
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
if domain1:
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE ' + red_sql + ' ' + domain1,
red_ids + domain2)
else:
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE ' + red_sql, red_ids)
rowcount = cursor.rowcount
if rowcount == -1 or rowcount is None:
rowcount = len(cursor.fetchall())
if not rowcount == len({}.fromkeys(sub_ids)):
if domain1:
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE ' + red_sql, red_ids)
rowcount = cursor.rowcount
if rowcount == -1 or rowcount is None:
rowcount = len(cursor.fetchall())
if rowcount == len({}.fromkeys(sub_ids)):
self.raise_user_error('access_error',
self._description)
self.raise_user_error('write_error', self._description)
try:
cursor.execute('UPDATE "' + self._table + '" ' \
'SET ' + \
','.join([x[0] + ' = '+ x[1] for x in upd0]) + ' ' \
'WHERE ' + red_sql, upd1 + red_ids)
except DatabaseIntegrityError, exception:
with contextlib.nested(Transaction().new_cursor(),
Transaction().set_user(0)):
for field_name in values:
if field_name not in self._columns:
continue
field = self._columns[field_name]
# Check required fields
if field.required and \
not hasattr(field, 'set') and \
not isinstance(field, (fields.Integer,
fields.Float)) and \
field_name not in ('create_uid',
'create_date'):
if not values[field_name]:
self.raise_user_error('required_field',
error_args=self._get_error_args(
field_name))
if isinstance(field, fields.Many2One) \
and values[field_name]:
model_obj = self.pool.get(field.model_name)
create_records = Transaction().create_records.get(
field.model_name, set())
delete_records = Transaction().delete_records.get(
field.model_name, set())
if not ((model_obj.search([
('id', '=', values[field_name]),
], order=[])
or values[field_name] in create_records)
and values[field_name] not in delete_records):
self.raise_user_error( 'foreign_model_missing',
error_args=self._get_error_args(
field_name))
for name, _, error in self._sql_constraints:
if name in exception[0]:
self.raise_user_error(error)
for name, error in self._sql_error_messages:
if name in exception[0]:
self.raise_user_error(error)
raise
for field in direct:
if getattr(self._columns[field], 'translate', False):
self.pool.get('ir.translation')._set_ids(
self._name + ',' + field, 'model',
Transaction().language, ids, values[field])
# call the 'set' method of fields
upd_todo.sort(lambda x, y: self._columns[x].priority - \
self._columns[y].priority)
for field in upd_todo:
self._columns[field].set(ids, self, field, values[field])
if self._history:
columns = ['"' + str(x) + '"' for x in self._columns
if not hasattr(self._columns[x], 'set')]
for obj_id in ids:
cursor.execute('INSERT INTO "' + self._table + '__history" ' \
'(' + ','.join(columns) + ') ' \
'SELECT ' + ','.join(columns) + ' ' + \
'FROM "' + self._table + '" ' \
'WHERE id = %s', (obj_id,))
for table in self._inherits:
col = self._inherits[table]
nids = []
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
cursor.execute('SELECT DISTINCT "' + col + '" ' \
'FROM "' + self._table + '" WHERE ' + red_sql,
red_ids)
nids.extend([x[0] for x in cursor.fetchall()])
values2 = {}
for val in updend:
if self._inherit_fields[val][0] == table:
values2[val] = values[val]
self.pool.get(table).write(nids, values2)
self._validate(ids)
# Check for Modified Preorder Tree Traversal
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
if field.left in values or field.right in values:
raise Exception('ValidateError',
'You can not update fields: "%s", "%s"' %
(field.left, field.right))
if len(ids) == 1:
self._update_tree(ids[0], k, field.left, field.right)
else:
with Transaction().set_user(0):
self._rebuild_tree(k, False, 0)
self.trigger_write(trigger_eligibles)
return True
def delete(self, ids):
cursor = Transaction().cursor
if not ids:
return True
if self.table_query():
return True
if isinstance(ids, (int, long)):
ids = [ids]
if Transaction().delete and Transaction().delete.get(self._name):
ids = ids[:]
for del_id in Transaction().delete[self._name]:
for i in range(ids.count(del_id)):
ids.remove(del_id)
if Transaction().timestamp:
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
clause = ('(id = %s AND '
'CAST(EXTRACT(EPOCH FROM '
'COALESCE(write_date, create_date)) AS ' + \
FIELDS['numeric'].sql_type(self.create_date)[1] + \
') > %s)')
args = []
for i in sub_ids:
if Transaction().timestamp.get(self._name + ',' + str(i)):
args.append(i)
args.append(Transaction().timestamp[
self._name + ',' +str(i)])
if args:
cursor.execute("SELECT id " \
'FROM "' + self._table + '" ' \
'WHERE ' + ' OR '.join(
(clause,) * (len(args)/2)), args)
if cursor.fetchone():
raise Exception('ConcurrencyException',
'Records were modified in the meanwhile')
for i in ids:
if Transaction().timestamp.get(self._name + ',' + str(i)):
del Transaction().timestamp[self._name + ',' +str(i)]
tree_ids = {}
for k in self._columns:
field = self._columns[k]
if isinstance(field, fields.Many2One) \
and field.model_name == self._name \
and field.left and field.right:
red_sql, red_ids = reduce_ids('"' + k + '"', ids)
cursor.execute('SELECT id FROM "' + self._table + '" '\
'WHERE ' + red_sql, red_ids)
tree_ids[k] = [x[0] for x in cursor.fetchall()]
foreign_keys_tocheck = []
foreign_keys_toupdate = []
foreign_keys_todelete = []
for _, model in self.pool.iterobject():
if hasattr(model, 'table_query') \
and model.table_query():
continue
for field_name, field in model._columns.iteritems():
if isinstance(field, fields.Many2One) \
and field.model_name == self._name:
if field.ondelete == 'CASCADE':
foreign_keys_todelete.append((model, field_name))
elif field.ondelete == 'SET NULL':
if field.required:
foreign_keys_tocheck.append((model, field_name))
else:
foreign_keys_toupdate.append((model, field_name))
else:
foreign_keys_tocheck.append((model, field_name))
Transaction().delete.setdefault(self._name, set()).update(ids)
domain1, domain2 = self.pool.get('ir.rule').domain_get(self._name,
mode='delete')
if domain1:
domain1 = ' AND (' + domain1 + ') '
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
if domain1:
cursor.execute('SELECT id FROM "'+self._table+'" ' \
'WHERE ' + red_sql + ' ' + domain1,
red_ids + domain2)
rowcount = cursor.rowcount
if rowcount == -1 or rowcount is None:
rowcount = len(cursor.fetchall())
if not rowcount == len({}.fromkeys(sub_ids)):
self.raise_user_error('access_error', self._description)
self.trigger_delete(ids)
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
Transaction().delete_records.setdefault(self._name,
set()).update(sub_ids)
for model, field_name in foreign_keys_toupdate:
if not hasattr(model, 'search') \
or not hasattr(model, 'write'):
continue
red_sql2, red_ids2 = reduce_ids('"' + field_name + '"', sub_ids)
cursor.execute('SELECT id FROM "' + model._table + '" ' \
'WHERE ' + red_sql2, red_ids2)
model_ids = [x[0] for x in cursor.fetchall()]
if model_ids:
model.write(model_ids, {
field_name: False,
})
for model, field_name in foreign_keys_todelete:
if not hasattr(model, 'search') \
or not hasattr(model, 'delete'):
continue
red_sql2, red_ids2 = reduce_ids('"' + field_name + '"', sub_ids)
cursor.execute('SELECT id FROM "' + model._table + '" ' \
'WHERE ' + red_sql2, red_ids2)
model_ids = [x[0] for x in cursor.fetchall()]
if model_ids:
model.delete(model_ids)
for model, field_name in foreign_keys_tocheck:
with Transaction().set_user(0):
if model.search([
(field_name, 'in', sub_ids),
], order=[]):
error_args = []
error_args.append(self._get_error_args('id')[1])
error_args.extend(list(
model._get_error_args(field_name)))
self.raise_user_error('foreign_model_exist',
error_args=tuple(error_args))
super(ModelSQL, self).delete(sub_ids)
try:
cursor.execute('DELETE FROM "'+self._table+'" ' \
'WHERE ' + red_sql, red_ids)
except DatabaseIntegrityError:
with Transaction().new_cursor():
for name, _, error in self._sql_constraints:
if name in exception[0]:
self.raise_user_error(error)
for name, error in self._sql_error_messages:
if name in exception[0]:
self.raise_user_error(error)
raise
if self._history:
for obj_id in ids:
cursor.execute('INSERT INTO "' + self._table + '__history" ' \
'(id, write_uid, write_date) VALUES (%s, %s, %s)',
(obj_id, user, datetime.datetime.now()))
for k in tree_ids.keys():
field = self._columns[k]
if len(tree_ids[k]) == 1:
self._update_tree(tree_ids[k][0], k, field.left, field.right)
else:
with Transaction().set_user(0):
self._rebuild_tree(k, False, 0)
return True
def search(self, domain, offset=0, limit=None, order=None, count=False,
query_string=False):
rule_obj = self.pool.get('ir.rule')
cursor = Transaction().cursor
# Get domain clauses
qu1, qu2, tables, tables_args = self.search_domain(domain)
# Get order by
order_by = []
if order is None or order is False:
order = self._order
for field, otype in order:
if otype.upper() not in ('DESC', 'ASC'):
raise Exception('Error', 'Wrong order type (%s)!' % otype)
order_by2, tables2, tables2_args = self._order_calc(field, otype)
order_by += order_by2
for table in tables2:
if table not in tables:
tables.append(table)
if tables2_args.get(table):
tables_args.extend(tables2_args.get(table))
order_by = ','.join(order_by)
if type(limit) not in (float, int, long, type(None)):
raise Exception('Error', 'Wrong limit type (%s)!' % type(limit))
if type(offset) not in (float, int, long, type(None)):
raise Exception('Error', 'Wrong offset type (%s)!' % type(offset))
# construct a clause for the rules :
domain1, domain2 = rule_obj.domain_get(self._name, mode='read')
if domain1:
if qu1:
qu1 += ' AND ' + domain1
else:
qu1 = domain1
qu2 += domain2
if count:
cursor.execute(cursor.limit_clause(
'SELECT COUNT("%s".id) FROM ' % self._table +
' '.join(tables) + (qu1 and ' WHERE ' + qu1 or ''),
limit, offset), tables_args + qu2)
res = cursor.fetchall()
return res[0][0]
# execute the "main" query to fetch the ids we were searching for
select_fields = ['"' + self._table + '".id AS id']
if self._history and Transaction().context.get('_datetime') \
and not query_string:
select_fields += ['COALESCE("' + self._table + '".write_date, "' + \
self._table + '".create_date) AS _datetime']
if not query_string:
select_fields += [
'"' + self._table + '"."' + x[0] + '" AS "' + x[0] + '"' \
for x in self._columns.iteritems() \
if not hasattr(x[1], 'get') \
and (x[0] != 'id')
and (not getattr(x[1], 'translate', False) \
and x[1]._type not in ('text', 'binary'))]
if not self.table_query():
select_fields += ['CAST(EXTRACT(EPOCH FROM '
'(COALESCE("' + self._table + '".write_date, '
'"' + self._table + '".create_date))) AS ' + \
FIELDS['numeric'].sql_type(self.create_date)[1] + \
') AS _timestamp']
query_str = cursor.limit_clause(
'SELECT ' + ','.join(select_fields) + ' FROM ' + \
' '.join(tables) + (qu1 and ' WHERE ' + qu1 or '') + \
(order_by and ' ORDER BY ' + order_by or ''), limit, offset)
if query_string:
return (query_str, tables_args + qu2)
cursor.execute(cursor.limit_clause(
'SELECT * FROM (' + query_str + ') AS ' \
'"' + self._table + '"', cursor.IN_MAX), tables_args + qu2)
datas = cursor.dictfetchall()
cache = cursor.get_cache()
cache.setdefault(self._name, {})
delete_records = Transaction().delete_records.setdefault(self._name,
set())
keys = None
for data in datas:
if data['id'] in delete_records:
continue
if not keys:
keys = data.keys()
for k in keys[:]:
if k in ('_timestamp', '_datetime'):
keys.remove(k)
continue
field = self._columns[k]
if field._type not in ('many2one',):
keys.remove(k)
continue
for k in keys:
del data[k]
cache[self._name].setdefault(data['id'], {})
cache[self._name][data['id']].update(data)
if len(datas) >= cursor.IN_MAX:
select_fields2 = [select_fields[0]]
if (self._history
and Transaction().context.get('_datetime')
and not query_string):
select_fields2 += [select_fields[1]]
cursor.execute(
'SELECT * FROM (' + \
cursor.limit_clause(
'SELECT ' + ','.join(select_fields2) + ' FROM ' + \
' '.join(tables) + (qu1 and ' WHERE ' + qu1 or '') + \
(order_by and ' ORDER BY ' + order_by or ''),
limit, offset) + ') AS "' + self._table + '"',
tables_args + qu2)
datas = cursor.dictfetchall()
if self._history and Transaction().context.get('_datetime'):
res = []
ids_date = {}
for data in datas:
if data['id'] in ids_date:
if data['_datetime'] <= ids_date[data['id']]:
continue
if data['id'] in res:
res.remove(data['id'])
res.append(data['id'])
ids_date[data['id']] = data['_datetime']
return res
return [x['id'] for x in datas]
def search_domain(self, domain, active_test=True):
'''
Return SQL clause and arguments for the domain
:param domain: a domain like in search
:param active_test: a boolean to add 'active' test
:return: a tuple with
- a SQL clause string
- a list of arguments for the SQL clause
- a list of tables used in the SQL clause
- a list of arguments for the tables
'''
domain = self._search_domain_active(domain, active_test=active_test)
table_query = ''
tables_args = []
if self.table_query():
table_query, tables_args = self.table_query()
table_query = '(' + table_query + ') AS '
if self._history and Transaction().context.get('_datetime'):
table_query = '"' + self._table + '__history" AS '
tables = [table_query + '"' + self._table + '"']
qu1, qu2 = self.__search_domain_oper(domain, tables, tables_args)
if self._history and Transaction().context.get('_datetime'):
if qu1:
qu1 += ' AND'
qu1 += ' (COALESCE("' + self._table + '".write_date, "' + \
self._table + '".create_date) <= %s)'
qu2 += [Transaction().context['_datetime']]
return qu1, qu2, tables, tables_args
def __search_domain_oper(self, domain, tables, tables_args):
operator = 'AND'
if len(domain) and isinstance(domain[0], basestring):
if domain[0] not in ('AND', 'OR'):
raise Exception('ValidateError', 'Operator "%s" not supported' \
% domain[0])
operator = domain[0]
domain = domain[1:]
tuple_args = []
list_args = []
for arg in domain:
#add test for xmlrpc that doesn't handle tuple
if isinstance(arg, tuple) \
or (isinstance(arg, list) and len(arg) > 2 \
and arg[1] in OPERATORS):
tuple_args.append(tuple(arg))
elif isinstance(arg, list):
list_args.append(arg)
qu1, qu2 = self.__search_domain_calc(tuple_args, tables, tables_args)
if len(qu1):
qu1 = (' ' + operator + ' ').join(qu1)
else:
qu1 = ''
for domain2 in list_args:
qu1b, qu2b = self.__search_domain_oper(domain2, tables,
tables_args)
if not qu1b:
qu1b = FIELDS['boolean'].sql_format(True)
if qu1 and qu1b:
qu1 += ' ' + operator + ' ' + qu1b
elif qu1b:
qu1 = qu1b
qu2 += qu2b
if qu1:
qu1 = '(' + qu1 + ')'
return qu1, qu2
def __search_domain_calc(self, domain, tables, tables_args):
domain = domain[:]
cursor = Transaction().cursor
for arg in domain:
if arg[1] not in OPERATORS:
raise Exception('ValidateError', 'Argument "%s" not supported' \
% arg[1])
i = 0
joins = []
while i < len(domain):
table = self
fargs = domain[i][0].split('.', 1)
if fargs[0] in self._inherit_fields:
itable = self.pool.get(self._inherit_fields[fargs[0]][0])
table_query = ''
table_arg = []
if itable.table_query():
table_query, table_args = self.table_query()
table_query = '(' + table_query + ') AS '
table_join = 'LEFT JOIN ' + table_query + \
'"' + itable._table + '" ON ' \
'"%s".id = "%s".%s' % (itable._table, self._table,
self._inherits[itable._name])
if table_join not in tables:
tables.append(table_join)
tables_args.extend(table_arg)
field = table._columns.get(fargs[0], False)
if not field:
if not fargs[0] in self._inherit_fields:
raise Exception('ValidateError', 'Field "%s" doesn\'t ' \
'exist on "%s"' % (fargs[0], self._name))
table = self.pool.get(self._inherit_fields[fargs[0]][0])
field = table._columns.get(fargs[0], False)
if len(fargs) > 1:
if field._type == 'many2one':
if hasattr(field, 'search'):
domain.extend([(fargs[0], 'in',
self.pool.get(field.model_name).search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[]))])
domain.pop(i)
else:
domain[i] = (fargs[0], 'inselect',
self.pool.get(field.model_name).search([
(fargs[1], domain[i][1], domain[i][2]),
], order=[], query_string=True),
table)
i += 1
continue
else:
raise Exception('ValidateError', 'Clause on field "%s" ' \
'doesn\'t work on "%s"' %
(domain[i][0], self._name))
if hasattr(field, 'search'):
clause = domain.pop(i)
domain.extend(field.search(table, clause[0], clause))
elif field._type == 'one2many':
field_obj = self.pool.get(field.model_name)
if isinstance(domain[i][2], basestring):
# get the ids of the records of the "distant" resource
ids2 = [x[0] for x in field_obj.search([
('rec_name', domain[i][1], domain[i][2]),
], order=[])]
else:
ids2 = domain[i][2]
table_query = ''
table_args = []
if field_obj.table_query():
table_query, table_args = field_obj.table_query()
table_query = '(' + table_query + ') AS '
if ids2 == True or ids2 == False:
query1 = 'SELECT "' + field.field + '" ' \
'FROM ' + table_query + \
'"' + field_obj._table + '" ' \
'WHERE "' + field.field + '" IS NOT NULL'
query2 = table_args
clause = 'inselect'
if ids2 == False:
clause = 'notinselect'
domain[i] = ('id', clause, (query1, query2))
elif not ids2:
domain[i] = ('id', '=', '0')
else:
if len(ids2) < cursor.IN_MAX:
red_sql, red_ids = reduce_ids('id', ids2)
query1 = 'SELECT "' + field.field + '" ' \
'FROM ' + table_query + \
'"' + field_obj._table + '" ' \
'WHERE ' + red_sql
query2 = table_args + red_ids
domain[i] = ('id', 'inselect', (query1, query2))
else:
ids3 = []
for i in range(0, len(ids2), cursor.IN_MAX):
sub_ids2 = ids2[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids2)
cursor.execute(
'SELECT "' + field.field + '" ' \
'FROM ' + table_query + \
'"' + field_obj._table + '" ' \
'WHERE ' + red_sql,
table_args + red_ids)
ids3.extend([x[0] for x in cursor.fetchall()])
domain[i] = ('id', 'in', ids3)
i += 1
elif field._type == 'many2many':
# XXX must find a solution for long id list
if hasattr(field, 'model_name'):
target_obj = self.pool.get(field.model_name)
else:
target_obj = field.get_target(self.pool)
if domain[i][1] in ('child_of', 'not child_of'):
if isinstance(domain[i][2], basestring):
ids2 = [x[0] for x in target_obj.search([
('rec_name', 'ilike', domain[i][2]),
], order=[])]
elif isinstance(domain[i][2], (int, long)):
ids2 = [domain[i][2]]
else:
ids2 = domain[i][2]
def _rec_get(ids, table, parent):
if not ids:
return []
ids2 = table.search([
(parent, 'in', ids),
(parent, '!=', False),
], order=[])
return ids + _rec_get(ids2, table, parent)
if target_obj._name != table._name:
if len(domain[i]) != 4:
raise Exception('Error', 'Programming error: ' \
'child_of on field "%s" is not allowed!' % \
(domain[i][0],))
ids2 = target_obj.search([
(domain[i][3], 'child_of', ids2),
], order=[])
relation_obj = self.pool.get(field.relation_name)
red_sql, red_ids = reduce_ids('"' + field.target + '"',
ids2)
query1 = 'SELECT "' + field.origin + '" ' \
'FROM "' + relation_obj._table + '" ' \
'WHERE ' + red_sql + ' ' \
'AND "' + field.origin + '" IS NOT NULL'
query2 = red_ids
if domain[i][1] == 'child_of':
domain[i] = ('id', 'inselect', (query1, query2))
else:
domain[i] = ('id', 'notinselect', (query1, query2))
else:
if domain[i][1] == 'child_of':
domain[i] = ('id', 'in', ids2 + _rec_get(ids2,
table, domain[i][0]))
else:
domain[i] = ('id', 'not in', ids2 + _rec_get(ids2,
table, domain[i][0]))
else:
if isinstance(domain[i][2], basestring):
res_ids = [x[0] for x in target_obj.search([
('rec_name', domain[i][1], domain[i][2]),
], order=[])]
elif isinstance(domain[i][2], (int, long)) \
and not isinstance(domain[i][2], bool):
res_ids = [domain[i][2]]
else:
res_ids = domain[i][2]
if res_ids == True or res_ids == False:
relation_obj = self.pool.get(field.relation_name)
query1 = 'SELECT "' + field.origin + '" ' \
'FROM "' + relation_obj._table + '" '\
'WHERE "' + field.origin + '" IS NOT NULL'
query2 = []
clause = 'inselect'
if res_ids == False:
clause = 'notinselect'
domain[i] = ('id', clause, (query1, query2))
elif not res_ids:
domain[i] = ('id', '=', '0')
else:
relation_obj = self.pool.get(field.relation_name)
red_sql, red_ids = reduce_ids('"' + field.target + '"',
res_ids)
query1 = 'SELECT "' + field.origin + '" ' \
'FROM "' + relation_obj._table + '" ' \
'WHERE ' + red_sql
query2 = red_ids
domain[i] = ('id', 'inselect', (query1, query2))
i += 1
elif field._type == 'many2one':
# XXX must find a solution for long id list
if domain[i][1] in ('child_of', 'not child_of'):
if isinstance(domain[i][2], basestring):
field_obj = self.pool.get(field.model_name)
ids2 = [x[0] for x in field_obj.search([
('rec_name', 'like', domain[i][2]),
], order=[])]
elif isinstance(domain[i][2], (int, long)):
ids2 = [domain[i][2]]
else:
ids2 = domain[i][2]
def _rec_get(ids, table, parent):
if not ids:
return []
ids2 = table.search([
(parent, 'in', ids),
(parent, '!=', False),
])
return ids + _rec_get(ids2, table, parent)
if field.model_name != table._name:
if len(domain[i]) != 4:
raise Exception('Error', 'Programming error: ' \
'child_of on field "%s" is not allowed!' % \
(domain[i][0],))
ids2 = self.pool.get(field.model_name).search([
(domain[i][3], 'child_of', ids2),
], order=[])
if domain[i][1] == 'child_of':
domain[i] = (domain[i][0], 'in', ids2, table)
else:
domain[i] = (domain[i][0], 'not in', ids2, table)
else:
if field.left and field.right and ids2:
red_sql, red_ids = reduce_ids('id', ids2)
cursor.execute('SELECT "' + field.left + '", ' \
'"' + field.right + '" ' + \
'FROM "' + self._table + '" ' + \
'WHERE ' + red_sql, red_ids)
clause = FIELDS['boolean'].sql_format(False) + ' '
for left, right in cursor.fetchall():
clause += 'OR '
clause += '( "' + field.left + '" >= ' + \
str(left) + ' ' + \
'AND "' + field.right + '" <= ' + \
str(right) + ')'
query = 'SELECT id FROM "' + self._table + '" ' + \
'WHERE ' + clause
if domain[i][1] == 'child_of':
domain[i] = ('id', 'inselect', (query, []))
else:
domain[i] = ('id', 'notinselect', (query, []))
else:
if domain[i][1] == 'child_of':
domain[i] = ('id', 'in', ids2 + _rec_get(
ids2, table, domain[i][0]), table)
else:
domain[i] = ('id', 'not in', ids2 + _rec_get(
ids2, table, domain[i][0]), table)
else:
if isinstance(domain[i][2], basestring):
field_obj = self.pool.get(field.model_name)
res_ids = field_obj.search([
(field_obj._rec_name, domain[i][1], domain[i][2]),
], order=[])
domain[i] = (domain[i][0], 'in', res_ids, table)
else:
domain[i] += (table,)
i += 1
else:
if getattr(field, 'translate', False):
if self._name == 'ir.model':
table_join = 'LEFT JOIN "ir_translation" ' \
'ON (ir_translation.name = ' \
'ir_model.model||\',%s\' ' \
'AND ir_translation.res_id = 0 ' \
'AND ir_translation.lang = %%s ' \
'AND ir_translation.type = \'model\' ' \
'AND ir_translation.fuzzy = %s)' % \
(domain[i][0],
FIELDS['boolean'].sql_format(False))
elif self._name == 'ir.model.field':
if domain[i][0] == 'field_description':
ttype = 'field'
else:
ttype = 'help'
table_join = 'LEFT JOIN "ir_model" ' \
'ON ir_model.id = ir_model_field.model ' \
'LEFT JOIN "ir_translation" ' \
'ON (ir_translation.name = ' \
'ir_model.model||\',\'||%s.name ' \
'AND ir_translation.res_id = 0 ' \
'AND ir_translation = %%s ' \
'AND ir_translation.type = \'%s\' ' \
'AND ir_translation.fuzzy = %s)' % \
(table._table, ttype,
FIELDS['boolean'].sql_format(False))
else:
table_join = 'LEFT JOIN "ir_translation" ' \
'ON (ir_translation.res_id = %s.id ' \
'AND ir_translation.name = \'%s,%s\' ' \
'AND ir_translation.lang = %%s ' \
'AND ir_translation.type = \'model\' ' \
'AND ir_translation.fuzzy = %s)' % \
(table._table, table._name, domain[i][0],
FIELDS['boolean'].sql_format(False))
table_join_args = [Transaction().language]
table_query = ''
table_args = []
if table.table_query():
table_query, table_args = table.table_query()
table_query = '(' + table_query + ') AS '
translation_obj = self.pool.get('ir.translation')
qu1, qu2, tables, table_args = \
translation_obj.search_domain([
('value', domain[i][1], domain[i][2]),
])
qu1 = qu1.replace('"ir_translation"."value"',
'COALESCE(NULLIF("ir_translation"."value", \'\'), '
'"%s"."%s")' % (table._table, domain[i][0]))
query1 = 'SELECT "' + table._table + '".id ' \
'FROM ' + table_query + '"' + table._table + '" ' \
+ table_join + ' WHERE ' + qu1
query2 = table_args + table_join_args + qu2
domain[i] = ('id', 'inselect', (query1, query2), table)
else:
domain[i] += (table,)
i += 1
domain.extend(joins)
qu1, qu2 = [], []
for arg in domain:
table = self
if len(arg) > 3:
table = arg[3]
if arg[1] in ('inselect', 'notinselect'):
clause = 'IN'
if arg[1] == 'notinselect':
clause = 'NOT IN'
qu1.append('("%s"."%s" %s (%s))' % (table._table, arg[0],
clause, arg[2][0]))
qu2 += arg[2][1]
elif arg[1] in ('in', 'not in'):
if len(arg[2]) > 0:
todel = []
if table._columns[arg[0]]._type != 'boolean':
for xitem in range(len(arg[2])):
if arg[2][xitem] == False \
and isinstance(arg[2][xitem],bool):
todel.append(xitem)
arg2 = arg[2][:]
for xitem in todel[::-1]:
del arg2[xitem]
arg2 = [FIELDS[table._columns[arg[0]]._type].sql_format(x)
for x in arg2]
if len(arg2):
if reduce(lambda x, y: x and isinstance(y, (int, long)),
arg2, True):
red_sql, red_ids = reduce_ids('"%s"."%s"' % \
(table._table, arg[0]), arg2)
if arg[1] == 'not in':
red_sql = '(NOT(' + red_sql + '))'
qu1.append(red_sql)
qu2 += red_ids
else:
qu1.append(('("%s"."%s" ' + arg[1] + ' (%s))') % \
(table._table, arg[0], ','.join(
('%s',) * len(arg2))))
qu2 += arg2
if todel:
if table._columns[arg[0]]._type == 'boolean':
if arg[1] == 'in':
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'"%s"."%s" = %%s)' % \
(table._table, arg[0])
qu2.append(False)
else:
qu1[-1] = '(' + qu1[-1] + ' AND ' \
'"%s"."%s" != %%s)' % \
(table._table, arg[0])
qu2.append(False)
else:
if arg[1] == 'in':
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'"%s"."%s" IS NULL)' % \
(table._table, arg[0])
else:
qu1[-1] = '(' + qu1[-1] + ' AND ' \
'"%s"."%s" IS NOT NULL)' % \
(table._table, arg[0])
elif todel:
if table._columns[arg[0]]._type == 'boolean':
if arg[1] == 'in':
qu1.append('("%s"."%s" = %%s)' % \
(table._table, arg[0]))
qu2.append(False)
else:
qu1.append('("%s"."%s" != %%s)' % \
(table._table, arg[0]))
qu2.append(False)
else:
if arg[1] == 'in':
qu1.append('("%s"."%s" IS NULL)' % \
(table._table, arg[0]))
else:
qu1.append('("%s"."%s" IS NOT NULL)' % \
(table._table, arg[0]))
else:
if arg[1] == 'in':
qu1.append(' %s')
qu2.append(False)
else:
qu1.append(' %s')
qu2.append(True)
else:
if (arg[2] is False) and (arg[1] == '='):
if table._columns[arg[0]]._type == 'boolean':
qu1.append('("%s"."%s" = %%s)' % \
(table._table, arg[0]))
qu2.append(False)
else:
qu1.append('("%s"."%s" IS NULL)' % \
(table._table, arg[0]))
elif (arg[2] is False) and (arg[1] == '!='):
qu1.append('("%s"."%s" IS NOT NULL)' % \
(table._table, arg[0]))
else:
if arg[0] == 'id':
qu1.append('("%s"."%s" %s %%s)' % \
(table._table, arg[0], arg[1]))
qu2.append(FIELDS[table._columns[arg[0]]._type].\
sql_format(arg[2]))
else:
add_null = False
if arg[1] in ('like', 'ilike'):
if not arg[2]:
qu2.append('%')
add_null = True
else:
qu2.append(arg[2])
elif arg[1] in ('not like', 'not ilike'):
if not arg[2]:
qu2.append('')
else:
qu2.append(arg[2])
add_null = True
else:
if arg[0] in table._columns:
qu2.append(FIELDS[
table._columns[arg[0]]._type
].sql_format(arg[2]))
qu1.append('("%s"."%s" %s %%s)' % (table._table,
arg[0], arg[1]))
if add_null:
qu1[-1] = '(' + qu1[-1] + ' OR ' \
'"' + table._table + '"."' + arg[0] + '"' \
' IS NULL)'
return qu1, qu2
def _order_calc(self, field, otype):
order_by = []
tables = []
tables_args = {}
field_name = None
table_name = None
link_field = None
if field in self._columns:
table_name = self._table
if not hasattr(self._columns[field], 'set'):
field_name = field
if self._columns[field].order_field:
field_name = self._columns[field].order_field
if isinstance(self._columns[field], fields.Many2One):
obj = self.pool.get(self._columns[field].model_name)
table_name = obj._table
link_field = field
field_name = None
if obj._rec_name in obj._columns:
field_name = obj._rec_name
if obj._order_name in obj._columns:
field_name = obj._order_name
if field_name:
order_by, tables, tables_args = obj._order_calc(field_name,
otype)
table_join = 'LEFT JOIN "' + table_name + '" AS ' \
'"' + table_name + '.' + link_field + '" ON ' \
'"%s.%s".id = "%s".%s' % (table_name, link_field,
self._table, link_field)
for i in range(len(order_by)):
if table_name in order_by[i]:
order_by[i] = order_by[i].replace(table_name,
table_name + '.' + link_field)
for i in range(len(tables)):
if table_name in tables[i]:
args = tables_args[tables[i]]
del tables_args[tables[i]]
tables[i] = tables[i].replace(table_name,
table_name + '.' + link_field)
tables_args[tables[i]] = args
if table_join not in tables:
tables.insert(0, table_join)
return order_by, tables, tables_args
obj2 = None
if obj._rec_name in obj._inherit_fields.keys():
obj2 = self.pool.get(
obj._inherit_fields[obj._rec_name][0])
field_name = obj._rec_name
if obj._order_name in obj._inherit_fields.keys():
obj2 = self.pool.get(
obj._inherit_fields[obj._order_name][0])
field_name = obj._order_name
if obj2 and field_name:
table_name2 = obj2._table
link_field2 = obj._inherits[obj2._name]
order_by, tables, tables_args = \
obj2._order_calc(field_name, otype)
table_join = 'LEFT JOIN "' + table_name + '" AS ' \
'"' + table_name + '.' + link_field + '" ON ' \
'"%s.%s".id = "%s".%s' % \
(table_name, link_field, self._table, link_field)
for i in range(len(order_by)):
if table_name in order_by[i]:
order_by[i] = order_by[i].replace(table_name,
table_name + '.' + link_field)
for i in range(len(tables)):
if table_name in tables[i]:
args = tables_args[tables[i]]
del tables_args[tables[i]]
tables[i] = tables[i].replace(table_name,
table_name + '.' + link_field)
tables_args[tables[i]] = args
if table_join not in tables:
tables.insert(0, table_join)
table_join2 = 'LEFT JOIN "' + table_name2 + '" AS ' \
'"' + table_name2 + '.' + link_field2 + '" ON ' \
'"%s.%s".id = "%s.%s".%s' % \
(table_name2, link_field2, table_name, link_field,
link_field2)
for i in range(len(order_by)):
if table_name2 in order_by[i]:
order_by[i] = order_by[i].replace(table_name2,
table_name2 + '.' + link_field2)
for i in range(len(tables)):
if table_name2 in tables[i]:
args = tables_args[tables[i]]
del tables_args[tables[i]]
tables[i] = tables[i].replace(table_name2,
table_name2 + '.' + link_field2)
tables_args[tables[i]] = args
if table_join2 not in tables:
tables.insert(1, table_join2)
return order_by, tables, tables_args
if field_name in self._columns \
and getattr(self._columns[field_name], 'translate', False):
translation_table = 'ir_translation_%s_%s' % \
(table_name, field_name)
if self._name == 'ir.model':
table_join = 'LEFT JOIN "ir_translation" ' \
'AS "%s" ON ' \
'("%s".name = "ir_model".model||\',%s\' ' \
'AND "%s".res_id = 0 ' \
'AND "%s".lang = %%s ' \
'AND "%s".type = \'model\' ' \
'AND "%s".fuzzy = %s)' % \
(translation_table, translation_table, field_name,
translation_table, translation_table,
translation_table, translation_table,
FIELDS['boolean'].sql_format(False))
elif self._name == 'ir.model.field':
if field_name == 'field_description':
ttype = 'field'
else:
ttype = 'help'
table_join = 'LEFT JOIN "ir_model" ON ' \
'ir_model.id = ir_model_field.model'
if table_join not in tables:
tables.append(table_join)
table_join = 'LEFT JOIN "ir_translation" ' \
'AS "%s" ON ' \
'("%s".name = "ir_model".model||\',\'||%s.name ' \
'AND "%s".res_id = 0 ' \
'AND "%s".lang = %%s ' \
'AND "%s".type = \'%s\' ' \
'AND "%s".fuzzy = %s)' % \
(translation_table, translation_table, table_name,
translation_table, translation_table,
translation_table, ttype, translation_table,
FIELDS['boolean'].sql_format(False))
else:
table_join = 'LEFT JOIN "ir_translation" ' \
'AS "%s" ON ' \
'("%s".res_id = "%s".id ' \
'AND "%s".name = \'%s,%s\' ' \
'AND "%s".lang = %%s ' \
'AND "%s".type = \'model\' ' \
'AND "%s".fuzzy = %s)' % \
(translation_table, translation_table, table_name,
translation_table, self._name, field_name,
translation_table, translation_table,
translation_table,
FIELDS['boolean'].sql_format(False))
if table_join not in tables:
tables.append(table_join)
tables_args[table_join] = [Transaction().language]
order_by.append('COALESCE(NULLIF(' \
+ '"' + translation_table + '".value, \'\'), ' \
+ '"' + table_name + '".' + field_name + ') ' + otype)
return order_by, tables, tables_args
if field_name in self._columns \
and self._columns[field_name]._type == 'selection' \
and self._columns[field_name].order_field is None:
selections = self.fields_get([field_name]
)[field_name]['selection']
if not isinstance(selections, (tuple, list)):
selections = getattr(self,
self._columns[field_name].selection)()
order = 'CASE ' + table_name + '.' + field_name
for selection in selections:
order += ' WHEN \'%s\' THEN \'%s\'' % selection
order += ' ELSE ' + table_name + '.' + field_name + ' END'
order_by.append(order + ' ' + otype)
return order_by, tables, tables_args
if field_name:
if '%(table)s' in field_name or '%(order)s' in field_name:
order_by.append(field_name % {
'table': table_name,
'order': otype,
})
else:
order_by.append('"' + table_name + '".' + field_name + \
' ' + otype)
return order_by, tables, tables_args
if field in self._inherit_fields.keys():
obj = self.pool.get(self._inherit_fields[field][0])
table_name = obj._table
link_field = self._inherits[obj._name]
order_by, tables, tables_args = obj._order_calc(field, otype)
table_join = 'LEFT JOIN "' + table_name + '" ON ' \
'"%s".id = "%s".%s' % \
(table_name, self._table, link_field)
if table_join not in tables:
tables.insert(0, table_join)
return order_by, tables, tables_args
raise Exception('Error', 'Wrong field name (%s) in order!' % field)
def _rebuild_tree(self, parent, parent_id, left):
'''
Rebuild left, right value for the tree.
'''
cursor = Transaction().cursor
right = left + 1
with Transaction().set_user(0):
child_ids = self.search([
(parent, '=', parent_id),
])
for child_id in child_ids:
right = self._rebuild_tree(parent, child_id, right)
field = self._columns[parent]
if parent_id:
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + field.left + '" = %s, ' \
'"' + field.right + '" = %s ' \
'WHERE id = %s', (left, right, parent_id))
return right + 1
def _update_tree(self, object_id, field_name, left, right):
'''
Update left, right values for the tree.
Remarks:
- the value (right - left - 1) / 2 will not give
the number of children node
- the order of the tree respects the default _order
'''
cursor = Transaction().cursor
cursor.execute('SELECT "' + left + '", "' + right + '" ' \
'FROM "' + self._table + '" ' \
'WHERE id = %s', (object_id,))
fetchone = cursor.fetchone()
if not fetchone:
return
old_left, old_right = fetchone
if old_left == old_right:
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + right + '" = "' + right + '" + 1 ' \
'WHERE id = %s', (object_id,))
old_right += 1
parent_right = 1
cursor.execute('SELECT "' + field_name + '" ' \
'FROM "' + self._table + '" ' \
'WHERE id = %s', (object_id,))
parent_id = cursor.fetchone()[0] or False
if parent_id:
cursor.execute('SELECT "' + right + '" ' \
'FROM "' + self._table + '" ' \
'WHERE id = %s', (parent_id,))
parent_right = cursor.fetchone()[0]
else:
cursor.execute('SELECT MAX("' + right + '") ' \
'FROM "' + self._table + '" ' \
'WHERE "' + field_name + '" IS NULL')
fetchone = cursor.fetchone()
if fetchone:
parent_right = fetchone[0] + 1
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
(old_left, old_right))
child_ids = [x[0] for x in cursor.fetchall()]
if len(child_ids) > cursor.IN_MAX:
with Transaction().set_user(0):
return self._rebuild_tree(field_name, False, 0)
red_child_sql, red_child_ids = reduce_ids('id', child_ids)
# ids for left update
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE "' + left + '" >= %s ' \
'AND NOT ' + red_child_sql,
[parent_right] + red_child_ids)
left_ids = [x[0] for x in cursor.fetchall()]
# ids for right update
cursor.execute('SELECT id FROM "' + self._table + '" ' \
'WHERE "' + right + '" >= %s ' \
'AND NOT ' + red_child_sql,
[parent_right] + red_child_ids)
right_ids = [x[0] for x in cursor.fetchall()]
if left_ids:
for i in range(0, len(left_ids), cursor.IN_MAX):
sub_ids = left_ids[i:i + cursor.IN_MAX]
red_sub_sql, red_sub_ids = reduce_ids('id', sub_ids)
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + left + '" = "' + left + '" + ' \
+ str(old_right - old_left + 1) + ' ' \
'WHERE ' + red_sub_sql, red_sub_ids)
if right_ids:
for i in range(0, len(right_ids), cursor.IN_MAX):
sub_ids = right_ids[i:i + cursor.IN_MAX]
red_sub_sql, red_sub_ids = reduce_ids('id', sub_ids)
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + right + '" = "' + right + '" + ' \
+ str(old_right - old_left + 1) + ' ' \
'WHERE ' + red_sub_sql, red_sub_ids)
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + left + '" = "' + left + '" + ' \
+ str(parent_right - old_left) + ', ' \
'"' + right + '" = "' + right + '" + ' \
+ str(parent_right - old_left) + ' ' \
'WHERE ' + red_child_sql, red_child_ids)
# Use root user to by-pass rules
with contextlib.nested(Transaction().set_user(0),
Transaction().set_context(active_test=False)):
brother_ids = self.search([
(field_name, '=', parent_id),
])
if brother_ids[-1] != object_id:
next_id = brother_ids[brother_ids.index(object_id) + 1]
cursor.execute('SELECT "' + left + '" ' \
'FROM "' + self._table + '" ' \
'WHERE id = %s', (next_id,))
next_left = cursor.fetchone()[0]
cursor.execute('SELECT "' + left + '" '\
'FROM "' + self._table + '" ' \
'WHERE id = %s', (object_id,))
current_left = cursor.fetchone()[0]
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + left + '" = "' + left + '" + ' \
+ str(old_right - old_left + 1) + ', ' \
'"' + right + '" = "' + right + '" + ' \
+ str(old_right - old_left + 1) + ' ' \
'WHERE "' + left + '" >= %s AND "' + right + '" <= %s',
(next_left, current_left))
cursor.execute('UPDATE "' + self._table + '" ' \
'SET "' + left + '" = "' + left + '" - ' \
+ str(current_left - next_left) + ', ' \
'"' + right + '" = "' + right + '" - ' \
+ str(current_left - next_left) + ' ' \
'WHERE ' + red_child_sql, red_child_ids)
def _validate(self, ids):
super(ModelSQL, self)._validate(ids)
cursor = Transaction().cursor
if cursor.has_constraint():
return
# Works only for a single transaction
for _, sql, error in self._sql_constraints:
match = _RE_UNIQUE.match(sql)
if match:
sql = match.group(1)
sql_clause = ' AND '.join('%s = %%s' % \
i for i in sql.split(','))
sql_clause = '(id != %s AND ' + sql_clause + ')'
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
cursor.execute('SELECT id,' + sql + ' ' \
'FROM "' + self._table + '" ' \
'WHERE ' + red_sql, red_ids)
fetchall = cursor.fetchall()
cursor.execute('SELECT id ' \
'FROM "' + self._table + '" ' \
'WHERE ' + \
' OR '.join((sql_clause,) * len(fetchall)),
reduce(lambda x, y: x + list(y), fetchall, []))
if cursor.fetchone():
self.raise_user_error(error)
continue
match = _RE_CHECK.match(sql)
if match:
sql = match.group(1)
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
cursor.execute('SELECT id ' \
'FROM "' + self._table + '" ' \
'WHERE NOT (' + sql + ') ' \
'AND ' + red_sql, red_ids)
if cursor.fetchone():
self.raise_user_error(error)
continue