Antony Lesuisse has proposed merging 
lp:~openerp-dev/openobject-server/trunk-translationsql-xrg into 
lp:openobject-server.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-translationsql-xrg/+merge/65028

xrg sql loading of translation, much faster.
-- 
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-translationsql-xrg/+merge/65028
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-server/trunk-translationsql-xrg.
=== modified file 'openerp/addons/base/base.sql'
--- openerp/addons/base/base.sql	2011-05-23 13:21:53 +0000
+++ openerp/addons/base/base.sql	2011-06-17 16:09:28 +0000
@@ -314,6 +314,12 @@
     primary key(id)
 );
 
+CREATE TABLE res_lang (
+    id serial PRIMARY KEY,
+    name VARCHAR(64) NOT NULL UNIQUE,
+    code VARCHAR(16) NOT NULL UNIQUE
+);
+
 CREATE TABLE ir_model_data (
     id serial NOT NULL,
     create_uid integer,

=== modified file 'openerp/addons/base/ir/ir_translation.py'
--- openerp/addons/base/ir/ir_translation.py	2011-06-08 14:50:33 +0000
+++ openerp/addons/base/ir/ir_translation.py	2011-06-17 16:09:28 +0000
@@ -21,6 +21,7 @@
 
 from osv import fields, osv
 import tools
+import logging
 
 TRANSLATION_TYPE = [
     ('field', 'Field'),
@@ -39,6 +40,115 @@
     ('sql_constraint', 'SQL Constraint')
 ]
 
+class ir_translation_import_cursor(object):
+    """Temporary cursor for optimizing mass insert into ir.translation
+
+    Open it (attached to a sql cursor), feed it with translation data and
+    finish() it in order to insert multiple translations in a batch.
+    """
+    _table_name = 'tmp_ir_translation_import'
+
+    def __init__(self, cr, uid, parent, context):
+        """ Initializer
+
+        Store some values, and also create a temporary SQL table to accept
+        the data.
+        @param parent an instance of ir.translation ORM model
+        """
+
+        self._cr = cr
+        self._uid = uid
+        self._context = context
+        self._overwrite = context.get('overwrite', False)
+        self._debug = False
+        self._parent_table = parent._table
+
+        # Note that Postgres will NOT inherit the constraints or indexes
+        # of ir_translation, so this copy will be much faster.
+
+        cr.execute('''CREATE TEMP TABLE %s(
+            imd_model VARCHAR(64),
+            imd_module VARCHAR(64),
+            imd_name VARCHAR(128)
+            ) INHERITS (%s) ''' % (self._table_name, self._parent_table))
+
+    def push(self, ddict):
+        """Feed a translation, as a dictionary, into the cursor
+        """
+
+        self._cr.execute("INSERT INTO " + self._table_name \
+                + """(name, lang, res_id, src, type,
+                        imd_model, imd_module, imd_name, value)
+                VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s)""",
+                (ddict['name'], ddict['lang'], ddict.get('res_id'), ddict['src'], ddict['type'],
+                    ddict.get('imd_model'), ddict.get('imd_module'), ddict.get('imd_name'),
+                    ddict['value']))
+
+    def finish(self):
+        """ Transfer the data from the temp table to ir.translation
+        """
+        logger = logging.getLogger('orm')
+
+        cr = self._cr
+        if self._debug:
+            cr.execute("SELECT count(*) FROM %s" % self._table_name)
+            c = cr.fetchone()[0]
+            logger.debug("ir.translation.cursor: We have %d entries to process", c)
+
+        # Step 1: resolve ir.model.data references to res_ids
+        cr.execute("""UPDATE %s AS ti
+            SET res_id = imd.res_id
+            FROM ir_model_data AS imd
+            WHERE ti.res_id IS NULL
+                AND ti.imd_module IS NOT NULL AND ti.imd_name IS NOT NULL
+
+                AND ti.imd_module = imd.module AND ti.imd_name = imd.name
+                AND ti.imd_model = imd.model; """ % self._table_name)
+
+        if self._debug:
+            cr.execute("SELECT imd_module, imd_model, imd_name FROM %s " \
+                "WHERE res_id IS NULL AND imd_module IS NOT NULL" % self._table_name)
+            for row in cr.fetchall():
+                logger.debug("ir.translation.cursor: missing res_id for %s. %s/%s ", *row)
+
+        cr.execute("DELETE FROM %s WHERE res_id IS NULL AND imd_module IS NOT NULL" % \
+            self._table_name)
+
+        # Records w/o res_id must _not_ be inserted into our db, because they are
+        # referencing non-existent data.
+
+        find_expr = "irt.lang = ti.lang AND irt.type = ti.type " \
+                    " AND irt.name = ti.name AND irt.src = ti.src " \
+                    " AND (ti.type != 'model' OR ti.res_id = irt.res_id) "
+
+        # Step 2: update existing (matching) translations
+        if self._overwrite:
+            cr.execute("""UPDATE ONLY %s AS irt
+                SET value = ti.value 
+                FROM %s AS ti
+                WHERE %s AND ti.value IS NOT NULL AND ti.value != ''
+                """ % (self._parent_table, self._table_name, find_expr))
+
+        # Step 3: insert new translations
+
+        cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value)
+            SELECT name, lang, res_id, src, type, value
+              FROM %s AS ti
+              WHERE NOT EXISTS(SELECT 1 FROM ONLY %s AS irt WHERE %s);
+              """ % (self._parent_table, self._table_name, self._parent_table, find_expr))
+
+        if self._debug:
+            cr.execute('SELECT COUNT(*) FROM ONLY %s' % (self._parent_table))
+            c1 = cr.fetchone()[0]
+            cr.execute('SELECT COUNT(*) FROM ONLY %s AS irt, %s AS ti WHERE %s' % \
+                (self._parent_table, self._table_name, find_expr))
+            c = cr.fetchone()[0]
+            logger.debug("ir.translation.cursor:  %d entries now in ir.translation, %d common entries with tmp", c1, c)
+
+        # Step 4: cleanup
+        cr.execute("DROP TABLE %s" % self._table_name)
+        return True
+
 class ir_translation(osv.osv):
     _name = "ir.translation"
     _log_access = False
@@ -56,13 +166,13 @@
         'type': fields.selection(TRANSLATION_TYPE, string='Type', size=16, select=True),
         'src': fields.text('Source'),
         'value': fields.text('Translation Value'),
-        # These two columns map to ir_model_data.module and ir_model_data.name.
-        # They are used to resolve the res_id above after loading is done.
-        'module': fields.char('Module', size=64, help='Maps to the ir_model_data for which this translation is provided.'),
-        'xml_id': fields.char('XML Id', size=128, help='Maps to the ir_model_data for which this translation is provided.'),
     }
-
-    def _auto_init(self, cr, context={}):
+    
+    _sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)', 
+        'Language code of translation item must be among known languages' ), ]
+
+
+    def _auto_init(self, cr, context=None):
         super(ir_translation, self)._auto_init(cr, context)
 
         # FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree. 
@@ -87,6 +197,11 @@
             cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
             cr.commit()
 
+    def _check_selection_field_value(self, cr, uid, field, value, context=None):
+        if field == 'lang':
+            return
+        return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context)
+    
     @tools.cache(skiparg=3, multi='ids')
     def _get_ids(self, cr, uid, name, tt, lang, ids):
         translations = dict.fromkeys(ids, False)
@@ -204,6 +319,13 @@
         result = super(ir_translation, self).unlink(cursor, user, ids, context=context)
         return result
 
+    def _get_import_cursor(self, cr, uid, context=None):
+        """ Return a cursor-like object for fast inserting translations
+        """
+        if context is None:
+            context = {}
+        return ir_translation_import_cursor(cr, uid, self, context=context)
+
 ir_translation()
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== modified file 'openerp/addons/base/module/module.py'
--- openerp/addons/base/module/module.py	2011-05-11 13:53:32 +0000
+++ openerp/addons/base/module/module.py	2011-06-17 16:09:28 +0000
@@ -519,7 +519,7 @@
                     tools.trans_load(cr, f, lang, verbose=False, context=context2)
                 elif iso_lang != 'en':
                     logger.warning('module %s: no translation for language %s', mod.name, iso_lang)
-        tools.trans_update_res_ids(cr)
+        # tools.trans_update_res_ids(cr)
 
     def check(self, cr, uid, ids, context=None):
         logger = logging.getLogger('init')

=== modified file 'openerp/tools/translate.py'
--- openerp/tools/translate.py	2011-06-14 14:22:26 +0000
+++ openerp/tools/translate.py	2011-06-17 16:09:28 +0000
@@ -891,6 +891,8 @@
 
         # read the rest of the file
         line = 1
+        irt_cursor = trans_obj._get_import_cursor(cr, uid, context=context)
+
         for row in reader:
             line += 1
             # skip empty rows and rows where the translation field (=last fiefd) is empty
@@ -901,57 +903,47 @@
             # {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
             #  'src': ..., 'value': ...}
             dic = {'lang': lang}
+            dic_module = False
             for i in range(len(f)):
                 if f[i] in ('module',):
                     continue
                 dic[f[i]] = row[i]
 
-            try:
-                dic['res_id'] = dic['res_id'] and int(dic['res_id']) or 0
-                dic['module'] = False
-                dic['xml_id'] = False
-            except:
-                split_id = dic['res_id'].split('.', 1)
-                dic['module'] = split_id[0]
-                dic['xml_id'] = split_id[1]
-                dic['res_id'] = False
+            # This would skip terms that fail to specify a res_id
+            if not dic.get('res_id', False):
+                continue
 
-            args = [
-                ('lang', '=', lang),
-                ('type', '=', dic['type']),
-                ('name', '=', dic['name']),
-                ('src', '=', dic['src']),
-            ]
-            if dic['type'] == 'model':
-                if dic['res_id'] is False:
-                    args.append(('module', '=', dic['module']))
-                    args.append(('xml_id', '=', dic['xml_id']))
-                else:
-                    args.append(('res_id', '=', dic['res_id']))
-            ids = trans_obj.search(cr, uid, args)
-            if ids:
-                if context.get('overwrite') and dic['value']:
-                    trans_obj.write(cr, uid, ids, {'value': dic['value']})
+            res_id = dic.pop('res_id')
+            if res_id and isinstance(res_id, (int, long)) \
+                or (isinstance(res_id, basestring) and res_id.isdigit()):
+                    dic['res_id'] = int(res_id)
             else:
-                trans_obj.create(cr, uid, dic)
+                try:
+                    tmodel = dic['name'].split(',')[0]
+                    if '.' in res_id:
+                        tmodule, tname = res_id.split('.', 1)
+                    else:
+                        tmodule = dic_module
+                        tname = res_id
+                    dic['imd_model'] = tmodel
+                    dic['imd_module'] = tmodule
+                    dic['imd_name'] =  tname
+
+                    dic['res_id'] = None
+                except Exception:
+                    logger.warning("Could not decode resource for %s, please fix the po file.",
+                                    dic['res_id'], exc_info=True)
+                    dic['res_id'] = None
+
+            irt_cursor.push(dic)
+
+        irt_cursor.finish()
         if verbose:
             logger.info("translation file loaded succesfully")
     except IOError:
         filename = '[lang: %s][format: %s]' % (iso_lang or 'new', fileformat)
         logger.exception("couldn't read translation file %s", filename)
 
-def trans_update_res_ids(cr):
-    cr.execute("""
-            UPDATE ir_translation
-            SET res_id = COALESCE ((SELECT ir_model_data.res_id
-                          FROM ir_model_data
-                          WHERE ir_translation.module = ir_model_data.module
-                              AND ir_translation.xml_id = ir_model_data.name), 0)
-            WHERE ir_translation.module is not null
-                AND ir_translation.xml_id is not null
-                AND ir_translation.res_id = 0;
-    """)
-
 def get_locales(lang=None):
     if lang is None:
         lang = locale.getdefaultlocale()[0]

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to