summaryrefslogtreecommitdiff
path: root/odoo/tools/sql.py
diff options
context:
space:
mode:
authorRaphael Collet <rco@openerp.com>2017-02-03 09:42:35 +0100
committerRaphael Collet <rco@openerp.com>2017-02-22 15:24:07 +0100
commitd024c7602146ba293c0082303fd476c8613daedc (patch)
tree1c9687cc5a5f6449be626ac67b6846096f174457 /odoo/tools/sql.py
parent8225f3649729eb930a2511d2d4bf7127e2f1eca9 (diff)
[REF] tools: add functions for SQL schema manipulation
This helps factoring out a certain number of similar queries, and removing a few methods from `BaseModel`.
Diffstat (limited to 'odoo/tools/sql.py')
-rw-r--r--odoo/tools/sql.py146
1 files changed, 146 insertions, 0 deletions
diff --git a/odoo/tools/sql.py b/odoo/tools/sql.py
index 0c67657db8d..6bdcbb05303 100644
--- a/odoo/tools/sql.py
+++ b/odoo/tools/sql.py
@@ -1,6 +1,152 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import logging
+import psycopg2
+
+_schema = logging.getLogger('odoo.schema')
+
+def table_exists(cr, tablename):
+ """ Return whether the given table exists. """
+ query = "SELECT 1 FROM pg_class WHERE relkind IN ('r','v') AND relname=%s"
+ cr.execute(query, (tablename,))
+ return cr.rowcount
+
+def table_kind(cr, tablename):
+ """ Return the kind of a table: 'r' for ordinary tables, 'v' for views. """
+ cr.execute("SELECT relkind FROM pg_class WHERE relname=%s", (tablename,))
+ return cr.fetchone()[0] if cr.rowcount else None
+
+def create_model_table(cr, tablename, comment=None):
+ """ Create the table for a model. """
+ cr.execute('CREATE TABLE "{}" (id SERIAL NOT NULL, PRIMARY KEY(id))'.format(tablename))
+ if comment:
+ cr.execute('COMMENT ON TABLE "{}" IS %s'.format(tablename), (comment,))
+ _schema.debug("Table %r: created", tablename)
+
+def table_columns(cr, tablename):
+ """ Return a dict mapping column names to their configuration. The latter is
+ a dict with the following keys: `relname` (table name), `attname`
+ (column name), `attlen`, `atttypmod`, `attnotnull` (whether it has a NOT
+ NULL constraint), `atthasdef` (whether it has a default value),
+ `typname` (data type name), `size` (varchar size).
+ """
+ # attlen is the number of bytes necessary to represent the type when the
+ # type has a fixed size. If the type has a varying size attlen is -1 and
+ # atttypmod is the size limit + 4, or -1 if there is no limit.
+ query = """ SELECT c.relname, a.attname, a.attlen, a.atttypmod,
+ a.attnotnull, a.atthasdef, t.typname,
+ CASE WHEN a.attlen=-1 THEN (
+ CASE WHEN a.atttypmod=-1 THEN 0 ELSE a.atttypmod-4 END
+ ) ELSE a.attlen END as size
+ FROM pg_class c, pg_attribute a, pg_type t
+ WHERE c.relname=%s AND c.oid=a.attrelid AND a.atttypid=t.oid """
+ cr.execute(query, (tablename,))
+ return {row['attname']: row for row in cr.dictfetchall()}
+
+def column_exists(cr, tablename, columnname):
+ """ Return whether the given column exists. """
+ query = """ SELECT 1 FROM pg_class c, pg_attribute a
+ WHERE c.relname=%s AND a.attname=%s AND c.oid=a.attrelid """
+ cr.execute(query, (tablename, columnname))
+ return cr.rowcount
+
+def create_column(cr, tablename, columnname, columntype, comment=None):
+ """ Create a column with the given type. """
+ cr.execute('ALTER TABLE "{}" ADD COLUMN "{}" {}'.format(tablename, columnname, columntype))
+ if comment:
+ cr.execute('COMMENT ON COLUMN "{}"."{}" IS %s'.format(tablename, columnname), (comment,))
+ _schema.debug("Table %r: added column %r of type %s", tablename, columnname, columntype)
+
+def rename_column(cr, tablename, columnname1, columnname2):
+ """ Rename the given column. """
+ cr.execute('ALTER TABLE "{}" RENAME COLUMN "{}" TO "{}"'.format(tablename, columnname1, columnname2))
+ _schema.debug("Table %r: renamed column %r to %r", tablename, columnname1, columnname2)
+
+def convert_column(cr, tablename, columnname, columntype):
+ """ Convert the column to the given type. """
+ try:
+ with cr.savepoint():
+ cr.execute('ALTER TABLE "{}" ALTER COLUMN "{}" TYPE {}'.format(tablename, columnname, columntype),
+ log_exceptions=False)
+ except psycopg2.NotSupportedError:
+ # can't do inplace change -> use a casted temp column
+ query = 'ALTER TABLE "{0}" RENAME COLUMN "{1}" TO __temp_type_cast; ' \
+ 'ALTER TABLE "{0}" ADD COLUMN "{1}" {2}; ' \
+ 'UPDATE "{0}" SET "{1}"= __temp_type_cast::{2}' \
+ 'ALTER TABLE "{0}" DROP COLUMN __temp_type_cast CASCADE'
+ cr.execute(query.format(tablename, columntype, columntype))
+ _schema.debug("Table %r: column %r changed to type %s", tablename, columntype, columntype)
+
+def set_not_null(cr, tablename, columnname):
+ """ Add a NOT NULL constraint on the given column. """
+ query = 'ALTER TABLE "{}" ALTER COLUMN "{}" SET NOT NULL'.format(tablename, columnname)
+ try:
+ with cr.savepoint():
+ cr.execute(query)
+ _schema.debug("Table %r: column %r: added constraint NOT NULL", tablename, columnname)
+ except Exception:
+ msg = "Table %r: unable to set NOT NULL on column %r!\n" \
+ "If you want to have it, you should update the records and execute manually:\n%s"
+ _schema.warning(msg, tablename, columnname, query, exc_info=True)
+
+def drop_not_null(cr, tablename, columnname):
+ """ Drop the NOT NULL constraint on the given column. """
+ cr.execute('ALTER TABLE "{}" ALTER COLUMN "{}" DROP NOT NULL'.format(tablename, columnname))
+ _schema.debug("Table %r: column %r: dropped constraint NOT NULL", tablename, columnname)
+
+def constraint_definition(cr, constraintname):
+ """ Return the given constraint's definition. """
+ cr.execute("SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname=%s", (constraintname,))
+ return cr.fetchone()[0] if cr.rowcount else None
+
+def add_constraint(cr, tablename, constraintname, definition):
+ """ Add a constraint on the given table. """
+ query = 'ALTER TABLE "{}" ADD CONSTRAINT "{}" {}'.format(tablename, constraintname, definition)
+ try:
+ with cr.savepoint():
+ cr.execute(query)
+ _schema.debug("Table %r: added constraint %r as %s", tablename, constraintname, definition)
+ except Exception:
+ msg = "Table %r: unable to add constraint %r!\n" \
+ "If you want to have it, you should update the records and execute manually:\n%s"
+ _schema.warning(msg, tablename, constraintname, query, exc_info=True)
+
+def drop_constraint(cr, tablename, constraintname):
+ """ drop the given constraint. """
+ try:
+ with cr.savepoint():
+ cr.execute('ALTER TABLE "{}" DROP CONSTRAINT "{}"'.format(tablename, constraintname))
+ _schema.debug("Table %r: dropped constraint %r", tablename, constraintname)
+ except Exception:
+ _schema.warning("Table %r: unable to drop constraint %r!", tablename, constraintname)
+
+def index_exists(cr, indexname):
+ """ Return whether the given index exists. """
+ cr.execute("SELECT 1 FROM pg_indexes WHERE indexname=%s", (indexname,))
+ return cr.rowcount
+
+def create_index(cr, indexname, tablename, expressions):
+ """ Create the given index unless it exists. """
+ if index_exists(cr, indexname):
+ return
+ args = ', '.join(expressions)
+ cr.execute('CREATE INDEX "{}" ON "{}" ({})'.format(indexname, tablename, args))
+ _schema.debug("Table %r: created index %r (%s)", tablename, indexname, args)
+
+def create_unique_index(cr, indexname, tablename, expressions):
+ """ Create the given index unless it exists. """
+ if index_exists(cr, indexname):
+ return
+ args = ', '.join(expressions)
+ cr.execute('CREATE UNIQUE INDEX "{}" ON "{}" ({})'.format(indexname, tablename, args))
+ _schema.debug("Table %r: created index %r (%s)", tablename, indexname, args)
+
+def drop_index(cr, indexname, tablename):
+ """ Drop the given index if it exists. """
+ cr.execute('DROP INDEX IF EXISTS "{}"'.format(indexname))
+ _schema.debug("Table %r: dropped index %r", tablename, indexname)
+
def drop_view_if_exists(cr, viewname):
cr.execute("DROP view IF EXISTS %s CASCADE" % (viewname,))
cr.commit()