From 5cee7184238e861e4f9a3ccd7fd975ac20f52291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Fri, 31 Mar 2017 12:44:22 +0200 Subject: [PATCH] Move low level handling of the SQL type to the backend This changeset defines on the field the (somehow) abstract SQL type a field should use letting the duty of specifying the real SQL type the database supports and the conversion to/from the python types to the backend. issue6351 review33951002 --- CHANGELOG | 1 + doc/ref/models/fields.rst | 6 +++ doc/topics/backend_types.rst | 68 ++++++++++++++++++++++++++ doc/topics/index.rst | 1 + trytond/backend/database.py | 12 +++++ trytond/backend/mysql/database.py | 24 ++++++++- trytond/backend/postgresql/database.py | 23 ++++++++- trytond/backend/sqlite/database.py | 24 +++++++-- trytond/model/fields/binary.py | 24 ++------- trytond/model/fields/boolean.py | 6 +-- trytond/model/fields/char.py | 20 ++------ trytond/model/fields/date.py | 67 +++++-------------------- trytond/model/fields/dict.py | 12 ++--- trytond/model/fields/field.py | 23 ++++++--- trytond/model/fields/float.py | 20 ++------ trytond/model/fields/integer.py | 30 ++---------- trytond/model/fields/many2many.py | 3 ++ trytond/model/fields/many2one.py | 20 ++------ trytond/model/fields/numeric.py | 15 ++---- trytond/model/fields/one2many.py | 3 ++ trytond/model/fields/reference.py | 16 ++---- trytond/model/fields/selection.py | 10 +--- trytond/model/fields/text.py | 6 +-- 23 files changed, 219 insertions(+), 215 deletions(-) create mode 100644 doc/topics/backend_types.rst diff --git a/CHANGELOG b/CHANGELOG index fe0de8c515..c2b1b4a626 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Use generic SQL type in field and let backend determine the SQLType * Add filter to xxx2Many fields * Add NULLS ordering * Add context domain on ir.action.act_window diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst index 96d77fc842..754ec470c1 100644 --- a/doc/ref/models/fields.rst +++ b/doc/ref/models/fields.rst @@ -174,6 +174,12 @@ Instance methods: use for creation and casting. Or `None` if the field is not stored in the database. + sql_type is using the `_sql_type` attribute to compute its return value. + The backend is responsible for the computation. + + For the list of supported types by Tryton see + :ref:`backend types `. + .. method:: Field.sql_column(table) Return the Column instance based on table. diff --git a/doc/topics/backend_types.rst b/doc/topics/backend_types.rst new file mode 100644 index 0000000000..00d73e41be --- /dev/null +++ b/doc/topics/backend_types.rst @@ -0,0 +1,68 @@ +.. _topics-backend_types: + +Backend Types supported +======================= + +This table give a comprehensive list of the SQL Types that are expected to be +supported by the database backends. If the type is not supported then the +backend will have to emulate the behavior described here. + +The columns are in the following order: + +* The SQL type [#]_ representing the field +* The python type expected on input +* The python type received on output + +.. [#] Corresponding to the `SQL 92`_ standard or to a `PostgreSQL type`_. +.. _`SQL 92`: http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt +.. _`PostgreSQL type`: https://www.postgresql.org/docs/current/static/datatype.html + +`None` will represent the `NULL` value and vice versa, it can be used as input +or output for any SQL type. + ++--------------------+----------------------+----------------------+ +| SQL Type | Python input type | Python output type | ++====================+======================+======================+ +| `BOOL` | bool | bool | ++--------------------+----------------------+----------------------+ +| `INTEGER` | int | int | ++--------------------+----------------------+----------------------+ +| `BIGINT` | int / long | int / long | +| | [#pyver_int]_ | [#pyver_int]_ | ++--------------------+----------------------+----------------------+ +| `FLOAT` | float / int / long | float | +| | [#pyver_int]_ | | ++--------------------+----------------------+----------------------+ +| `NUMERIC` | decimal.Decimal_ | decimal.Decimal_ | ++--------------------+----------------------+----------------------+ +| `VARCHAR` / | str / unicode | str / unicode | +| `VARCHAR(length)` | [#pyver_str]_ | [#pyver_str]_ | ++--------------------+----------------------+----------------------+ +| `TEXT` | str / unicode | str / unicode | +| | [#pyver_str]_ | [#pyver_str]_ | ++--------------------+----------------------+----------------------+ +| `TIMESTAMP` | datetime.datetime_ | datetime.datetime_ | ++--------------------+----------------------+----------------------+ +| `DATETIME` | datetime.datetime_ | datetime.datetime_ | +| | without microseconds | without microseconds | +| | [#utc_tz]_ | [#utc_tz]_ | ++--------------------+----------------------+----------------------+ +| `DATE` | datetime.date_ | datetime.date_ | ++--------------------+----------------------+----------------------+ +| `TIME` | datetime.time_ | datetime.time_ | ++--------------------+----------------------+----------------------+ +| `INTERVAL` | datetime.timedelta_ | datetime.timedelta_ | ++--------------------+----------------------+----------------------+ +| `BLOB` | bytes | bytes | ++--------------------+----------------------+----------------------+ + +.. [#pyver_int] in python 2 integers over *sys.maxint* are represented by the + `long` type +.. [#pyver_str] str when using python 3 ; unicode when using python 2 +.. [#utc_tz] Datetime objects are not localized to any timezone + +.. _datetime.date: https://docs.python.org/library/datetime.html#date-objects +.. _datetime.datetime: https://docs.python.org/library/datetime.html#datetime-objects +.. _datetime.time: https://docs.python.org/library/datetime.html#time-objects +.. _datetime.timedelta: https://docs.python.org/library/datetime.html#timedelta-objects +.. _decimal.Decimal: https://docs.python.org/library/decimal.html#decimal-objects diff --git a/doc/topics/index.rst b/doc/topics/index.rst index 6084d42e5a..f05bf192a1 100644 --- a/doc/topics/index.rst +++ b/doc/topics/index.rst @@ -15,6 +15,7 @@ Introduction to all the key parts of trytond: logs start_server models/index + backend-types models/fields_default_value models/fields_on_change domain diff --git a/trytond/backend/database.py b/trytond/backend/database.py index 9345f84516..fe92d04faf 100644 --- a/trytond/backend/database.py +++ b/trytond/backend/database.py @@ -1,8 +1,12 @@ # 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 collections import namedtuple + DatabaseIntegrityError = None DatabaseOperationalError = None +SQLType = namedtuple('SQLType', 'base type') + class DatabaseInterface(object): ''' @@ -159,3 +163,11 @@ class DatabaseInterface(object): def has_window_functions(self): "Return if database supports window functions." return False + + def sql_type(self, type_): + 'Return the SQLType tuple corresponding to the SQL type' + pass + + def sql_format(self, type_, value): + 'Return value correctly casted into type_' + pass diff --git a/trytond/backend/mysql/database.py b/trytond/backend/mysql/database.py index 98f0e6cc7d..73b7fec8cf 100644 --- a/trytond/backend/mysql/database.py +++ b/trytond/backend/mysql/database.py @@ -1,6 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.backend.database import DatabaseInterface +from trytond.backend.database import DatabaseInterface, SQLType from trytond.config import config, parse_uri import MySQLdb import MySQLdb.cursors @@ -79,6 +79,16 @@ class Database(DatabaseInterface): max_limit=18446744073709551610, function_mapping=MAPPING, null_ordering=False) + TYPES_MAPPING = { + 'BLOB': SQLType('LONGBLOB', 'LONGBLOB'), + 'DATETIME': SQLType('TIMESTAMP', 'TIMESTAMP NULL'), + 'TIMESTAMP': SQLType('TIMESTAMP', 'TIMESTAMP NULL'), + 'FLOAT': SQLType('DOUBLE', 'DOUBLE(255, 15)'), + 'INTEGER': SQLType('SIGNED INTEGER', 'BIGINT'), + 'BIGINTEGER': SQLType('SIGNED INTEGER', 'BIGINT'), + 'NUMERIC': SQLType('DECIMAL', 'DECIMAL(65, 30)'), + } + def connect(self): return self @@ -224,3 +234,15 @@ class Database(DatabaseInterface): cursor = connection.cursor() cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table, (value,)) + + def sql_type(self, type_): + if type_ in self.TYPES_MAPPING: + return self.TYPES_MAPPING[type_] + if type_.startswith('VARCHAR'): + return SQLType('CHAR', 'VARCHAR(255)') + return SQLType(type_, type_) + + def sql_format(self, type_, value): + if type_ == 'INTERVAL': + return value.total_seconds() + return value diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py index dd784a5481..cbacbe0de5 100644 --- a/trytond/backend/postgresql/database.py +++ b/trytond/backend/postgresql/database.py @@ -13,7 +13,7 @@ try: compat.register() except ImportError: pass -from psycopg2 import connect +from psycopg2 import connect, Binary from psycopg2.pool import ThreadedConnectionPool, PoolError from psycopg2.extensions import cursor from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ @@ -29,7 +29,7 @@ from psycopg2 import OperationalError as DatabaseOperationalError from sql import Flavor -from trytond.backend.database import DatabaseInterface +from trytond.backend.database import DatabaseInterface, SQLType from trytond.config import config, parse_uri __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] @@ -73,6 +73,12 @@ class Database(DatabaseInterface): _has_returning = None flavor = Flavor(ilike=True) + TYPES_MAPPING = { + 'BLOB': SQLType('BYTEA', 'BYTEA'), + 'DATETIME': SQLType('TIMESTAMP', 'TIMESTAMP(0)'), + 'TIMESTAMP': SQLType('TIMESTAMP', 'TIMESTAMP(6)'), + } + def __new__(cls, name='template1'): with cls._lock: if name in cls._databases: @@ -307,6 +313,19 @@ class Database(DatabaseInterface): def has_window_functions(self): return True + def sql_type(self, type_): + if type_ in self.TYPES_MAPPING: + return self.TYPES_MAPPING[type_] + if type_.startswith('VARCHAR'): + return SQLType('VARCHAR', type_) + return SQLType(type_, type_) + + def sql_format(self, type_, value): + if type_ == 'BLOB': + if value is not None: + return Binary(value) + return value + register_type(UNICODE) if PYDATE: register_type(PYDATE) diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py index be002ce9bc..809d2803f4 100644 --- a/trytond/backend/sqlite/database.py +++ b/trytond/backend/sqlite/database.py @@ -1,6 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.backend.database import DatabaseInterface +from trytond.backend.database import DatabaseInterface, SQLType from trytond.config import config import os from decimal import Decimal @@ -21,12 +21,11 @@ except ImportError: import sqlite3 as sqlite from sqlite3 import IntegrityError as DatabaseIntegrityError from sqlite3 import OperationalError as DatabaseOperationalError -from sql import Flavor, Table +from sql import Flavor, Table, Query, Expression from sql.functions import (Function, Extract, Position, Substring, Overlay, CharLength, CurrentTimestamp, Trim) __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] - logger = logging.getLogger(__name__) @@ -225,6 +224,11 @@ class Database(DatabaseInterface): paramstyle='qmark', function_mapping=MAPPING, null_ordering=False) IN_MAX = 200 + TYPES_MAPPING = { + 'DATETIME': SQLType('TIMESTAMP', 'TIMESTAMP'), + 'BIGINT': SQLType('INTEGER', 'INTEGER'), + } + def __new__(cls, name=':memory:'): if (name == ':memory:' and getattr(cls._local, 'memory_database', None)): @@ -402,6 +406,20 @@ class Database(DatabaseInterface): def has_multirow_insert(self): return True + def sql_type(self, type_): + if type_ in self.TYPES_MAPPING: + return self.TYPES_MAPPING[type_] + if type_.startswith('VARCHAR'): + return SQLType('VARCHAR', 'VARCHAR') + return SQLType(type_, type_) + + def sql_format(self, type_, value): + if type_ in ('INTEGER', 'BIGINT'): + if (value is not None + and not isinstance(value, (Query, Expression))): + value = int(value) + return value + sqlite.register_converter('NUMERIC', lambda val: Decimal(val.decode('utf-8'))) if sys.version_info[0] == 2: sqlite.register_adapter(Decimal, lambda val: buffer(str(val))) diff --git a/trytond/model/fields/binary.py b/trytond/model/fields/binary.py index 4dba060978..55f5249b37 100644 --- a/trytond/model/fields/binary.py +++ b/trytond/model/fields/binary.py @@ -1,10 +1,9 @@ # 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 sql import Query, Expression, Column, Null +from sql import Column, Null -from .field import Field, SQLType +from .field import Field from ...transaction import Transaction -from ... import backend from ...tools import grouped_slice, reduce_ids from ...filestore import filestore @@ -14,6 +13,7 @@ class Binary(Field): Define a binary field (``bytes``). ''' _type = 'binary' + _sql_type = 'BLOB' cast = bytearray if bytes == str else bytes def __init__(self, string='', help='', required=False, readonly=False, @@ -116,21 +116,3 @@ class Binary(Field): values = [self.sql_format(value)] cursor.execute(*table.update(columns, values, where=reduce_ids(table.id, ids))) - - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value - db_type = backend.name() - if db_type == 'postgresql' and value is not None: - import psycopg2 - return psycopg2.Binary(value) - return value - - def sql_type(self): - db_type = backend.name() - if db_type == 'postgresql': - return SQLType('BYTEA', 'BYTEA') - elif db_type == 'mysql': - return SQLType('LONGBLOB', 'LONGBLOB') - return SQLType('BLOB', 'BLOB') diff --git a/trytond/model/fields/boolean.py b/trytond/model/fields/boolean.py index dad5b63284..40c00297c9 100644 --- a/trytond/model/fields/boolean.py +++ b/trytond/model/fields/boolean.py @@ -1,7 +1,7 @@ # 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 .field import Field, SQLType +from .field import Field class Boolean(Field): @@ -9,6 +9,7 @@ class Boolean(Field): Define a boolean field (``True`` or ``False``). ''' _type = 'boolean' + _sql_type = 'BOOL' def __init__(self, string='', help='', readonly=False, domain=None, states=None, select=False, on_change=None, on_change_with=None, @@ -20,9 +21,6 @@ class Boolean(Field): __init__.__doc__ = Field.__init__.__doc__ - def sql_type(self): - return SQLType('BOOL', 'BOOL') - def _domain_add_null(self, column, operator, value, expression): expression = super(Boolean, self)._domain_add_null( column, operator, value, expression) diff --git a/trytond/model/fields/char.py b/trytond/model/fields/char.py index c3bef949dd..1ea74ac7d9 100644 --- a/trytond/model/fields/char.py +++ b/trytond/model/fields/char.py @@ -3,10 +3,7 @@ import sys import warnings -from sql import Query, Expression - -from ... import backend -from .field import Field, FieldTranslate, size_validate, SQLType +from .field import Field, FieldTranslate, size_validate from ...rpc import RPC @@ -49,10 +46,7 @@ class Char(FieldTranslate): size = property(_get_size, _set_size) - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None elif isinstance(value, str) and sys.version_info < (3,): @@ -60,13 +54,9 @@ class Char(FieldTranslate): assert isinstance(value, unicode) return value - def sql_type(self): - db_type = backend.name() - if self.size and db_type != 'sqlite': - return SQLType('VARCHAR', 'VARCHAR(%s)' % self.size) - elif db_type == 'mysql': - return SQLType('CHAR', 'VARCHAR(255)') - return SQLType('VARCHAR', 'VARCHAR') + @property + def _sql_type(self): + return 'VARCHAR(%s)' % self.size if self.size else 'VARCHAR' def set_rpc(self, model): super(Char, self).set_rpc(model) diff --git a/trytond/model/fields/date.py b/trytond/model/fields/date.py index e2b84452f7..0e67af156a 100644 --- a/trytond/model/fields/date.py +++ b/trytond/model/fields/date.py @@ -1,10 +1,8 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime -from sql import Query, Expression -from ... import backend -from .field import Field, SQLType +from .field import Field class Date(Field): @@ -12,11 +10,9 @@ class Date(Field): Define a date field (``date``). ''' _type = 'date' + _sql_type = 'DATE' - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None if isinstance(value, basestring): @@ -31,15 +27,13 @@ class Date(Field): or value.time() == datetime.time()) return value - def sql_type(self): - return SQLType('DATE', 'DATE') - class DateTime(Field): ''' Define a datetime field (``datetime``). ''' _type = 'datetime' + _sql_type = 'DATETIME' def __init__(self, string='', format='%H:%M:%S', help='', required=False, readonly=False, domain=None, states=None, select=False, @@ -56,10 +50,7 @@ class DateTime(Field): __init__.__doc__ += Field.__init__.__doc__ - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if not value: return None if isinstance(value, basestring): @@ -70,25 +61,15 @@ class DateTime(Field): assert(isinstance(value, datetime.datetime)) return value.replace(microsecond=0) - def sql_type(self): - db_type = backend.name() - if db_type == 'sqlite': - return SQLType('TIMESTAMP', 'TIMESTAMP') - elif db_type == 'mysql': - return SQLType('TIMESTAMP', 'TIMESTAMP NULL') - return SQLType('TIMESTAMP', 'TIMESTAMP(0)') - class Timestamp(Field): ''' Define a timestamp field (``datetime``). ''' _type = 'timestamp' + _sql_type = 'TIMESTAMP' - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None if isinstance(value, basestring): @@ -105,25 +86,15 @@ class Timestamp(Field): assert(isinstance(value, datetime.datetime)) return value - def sql_type(self): - db_type = backend.name() - if db_type == 'sqlite': - return SQLType('TIMESTAMP', 'TIMESTAMP') - elif db_type == 'mysql': - return SQLType('TIMESTAMP', 'TIMESTAMP NULL') - return SQLType('TIMESTAMP', 'TIMESTAMP(6)') - class Time(DateTime): ''' Define a time field (``time``). ''' _type = 'time' + _sql_type = 'TIME' - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None if isinstance(value, basestring): @@ -132,15 +103,13 @@ class Time(DateTime): assert(isinstance(value, datetime.time)) return value.replace(microsecond=0) - def sql_type(self): - return SQLType('TIME', 'TIME') - class TimeDelta(Field): ''' Define a timedelta field (``timedelta``). ''' _type = 'timedelta' + _sql_type = 'INTERVAL' def __init__(self, string='', converter=None, help='', required=False, readonly=False, domain=None, states=None, select=False, @@ -156,23 +125,11 @@ class TimeDelta(Field): depends=depends, context=context, loading=loading) self.converter = converter - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None assert(isinstance(value, datetime.timedelta)) - db_type = backend.name() - if db_type == 'mysql': - return value.total_seconds() - return value - - def sql_type(self): - db_type = backend.name() - if db_type == 'mysql': - return SQLType('DOUBLE', 'DOUBLE(255, 6)') - return SQLType('INTERVAL', 'INTERVAL') + return super(TimeDelta, self).sql_format(value) @classmethod def get(cls, ids, model, name, values=None): diff --git a/trytond/model/fields/dict.py b/trytond/model/fields/dict.py index 414587aa14..5b24dbefad 100644 --- a/trytond/model/fields/dict.py +++ b/trytond/model/fields/dict.py @@ -1,9 +1,8 @@ # This file is part of Tryton. The COPYRIGHT file at the toplevel of this # repository contains the full copyright notices and license terms. import json -from sql import Query, Expression -from .field import Field, SQLType +from .field import Field from ...protocols.jsonrpc import JSONDecoder, JSONEncoder from ...pool import Pool from ...tools import grouped_slice @@ -12,6 +11,7 @@ from ...tools import grouped_slice class Dict(Field): 'Define dict field.' _type = 'dict' + _sql_type = 'TEXT' def __init__(self, schema_model, string='', help='', required=False, readonly=False, domain=None, states=None, select=False, @@ -30,18 +30,12 @@ class Dict(Field): object_hook=JSONDecoder()) return dicts - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None assert isinstance(value, dict) return json.dumps(value, cls=JSONEncoder, separators=(',', ':')) - def sql_type(self): - return SQLType('TEXT', 'TEXT') - def translated(self, name=None, type_='values'): "Return a descriptor for the translated value of the field" if name is None: diff --git a/trytond/model/fields/field.py b/trytond/model/fields/field.py index 6926e47c68..f9f48f2241 100644 --- a/trytond/model/fields/field.py +++ b/trytond/model/fields/field.py @@ -1,13 +1,14 @@ # 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 collections import namedtuple import warnings from functools import wraps -from sql import operators, Column, Literal, Select, CombiningQuery, Null +from sql import (operators, Column, Literal, Select, CombiningQuery, Null, + Query, Expression) from sql.conditionals import Coalesce, NullIf from sql.operators import Concat +from trytond import backend from trytond.pyson import PYSON, PYSONEncoder, Eval from trytond.const import OPERATORS from trytond.transaction import Transaction @@ -16,6 +17,8 @@ from trytond.cache import LRUDictTransaction from ...rpc import RPC +Database = backend.get('Database') + def domain_validate(value): assert isinstance(value, list), 'domain must be a list' @@ -161,6 +164,7 @@ SQL_OPERATORS = { class Field(object): _type = None + _sql_type = None def __init__(self, string='', help='', required=False, readonly=False, domain=None, states=None, select=False, on_change=None, @@ -272,12 +276,17 @@ class Field(object): inst._values = {} inst._values[self.name] = value - @staticmethod - def sql_format(value): - return value + def sql_format(self, value): + if isinstance(value, (Query, Expression)): + return value + + assert self._sql_type is not None + database = Transaction().database + return database.sql_format(self._sql_type, value) def sql_type(self): - return None + database = Transaction().database + return database.sql_type(self._sql_type) def sql_column(self, table): return Column(table, self.name) @@ -438,5 +447,3 @@ class FieldTranslate(Field): return [Coalesce(NullIf(translation.value, ''), self.sql_column(table))] - -SQLType = namedtuple('SQLType', 'base type') diff --git a/trytond/model/fields/float.py b/trytond/model/fields/float.py index be716aff59..0fb103903d 100644 --- a/trytond/model/fields/float.py +++ b/trytond/model/fields/float.py @@ -1,9 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from sql import Query, Expression - -from ... import backend -from .field import Field, SQLType +from .field import Field from ...pyson import PYSON @@ -23,6 +20,7 @@ class Float(Field): Define a float field (``float``). ''' _type = 'float' + _sql_type = 'FLOAT' def __init__(self, string='', digits=None, help='', required=False, readonly=False, domain=None, states=None, select=False, @@ -50,19 +48,7 @@ class Float(Field): digits = property(_get_digits, _set_digits) - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None return float(value) - - def sql_type(self): - db_type = backend.name() - if db_type == 'postgresql': - return SQLType('FLOAT8', 'FLOAT8') - elif db_type == 'mysql': - return SQLType('DOUBLE', 'DOUBLE(255, 15)') - else: - return SQLType('FLOAT', 'FLOAT') diff --git a/trytond/model/fields/integer.py b/trytond/model/fields/integer.py index 720fa4a8eb..cd18477d67 100644 --- a/trytond/model/fields/integer.py +++ b/trytond/model/fields/integer.py @@ -1,9 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from sql import Query, Expression - -from ... import backend -from .field import Field, SQLType +from .field import Field class Integer(Field): @@ -11,23 +8,7 @@ class Integer(Field): Define an integer field (``int``). ''' _type = 'integer' - - def sql_type(self): - db_type = backend.name() - if db_type == 'postgresql': - return SQLType('INT4', 'INT4') - elif db_type == 'mysql': - return SQLType('SIGNED INTEGER', 'BIGINT') - else: - return SQLType('INTEGER', 'INTEGER') - - def sql_format(self, value): - db_type = backend.name() - if (db_type == 'sqlite' - and value is not None - and not isinstance(value, (Query, Expression))): - value = int(value) - return super(Integer, self).sql_format(value) + _sql_type = 'INTEGER' class BigInteger(Integer): @@ -35,9 +16,4 @@ class BigInteger(Integer): Define an integer field (``long``). ''' _type = 'biginteger' - - def sql_type(self): - db_type = backend.name() - if db_type == 'postgresql': - return SQLType('INT8', 'INT8') - return super(BigInteger, self).sql_type() + _sql_type = 'BIGINT' diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py index 214dbb1b85..09195c6784 100644 --- a/trytond/model/fields/many2many.py +++ b/trytond/model/fields/many2many.py @@ -79,6 +79,9 @@ class Many2Many(Field): def add_remove(self): return self.domain + def sql_type(self): + return None + def get(self, ids, model, name, values=None): ''' Return target records ordered. diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py index dbdf1d561f..0b9b9a3872 100644 --- a/trytond/model/fields/many2one.py +++ b/trytond/model/fields/many2one.py @@ -1,11 +1,10 @@ # 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 sql import Query, Expression, Literal +from sql import Literal from sql.operators import Or -from .field import Field, SQLType +from .field import Field from ...pool import Pool -from ... import backend from ...tools import reduce_ids from ...transaction import Transaction @@ -15,6 +14,7 @@ class Many2One(Field): Define many2one field (``int``). ''' _type = 'many2one' + _sql_type = 'INTEGER' def __init__(self, model_name, string='', left=None, right=None, ondelete='SET NULL', datetime_field=None, target_search='join', @@ -78,24 +78,12 @@ class Many2One(Field): assert isinstance(value, (Target, type(None))) super(Many2One, self).__set__(inst, value) - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None assert value is not False return int(value) - def sql_type(self): - db_type = backend.name() - if db_type == 'postgresql': - return SQLType('INT4', 'INT4') - elif db_type == 'mysql': - return SQLType('SIGNED INTEGER', 'BIGINT') - else: - return SQLType('INTEGER', 'INTEGER') - def convert_domain_mptt(self, domain, tables): cursor = Transaction().connection.cursor() table, _ = tables[None] diff --git a/trytond/model/fields/numeric.py b/trytond/model/fields/numeric.py index ce9abdc58b..ff2610a3e7 100644 --- a/trytond/model/fields/numeric.py +++ b/trytond/model/fields/numeric.py @@ -1,10 +1,9 @@ # 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 decimal import Decimal -from sql import Query, Expression, Cast, Literal, Select, CombiningQuery, As +from sql import Cast, Literal, Select, CombiningQuery, As from ... import backend -from .field import SQLType from .float import Float @@ -20,11 +19,9 @@ class Numeric(Float): Define a numeric field (``decimal``). ''' _type = 'numeric' + _sql_type = 'NUMERIC' - @staticmethod - def sql_format(value): - if isinstance(value, (Query, Expression)): - return value + def sql_format(self, value): if value is None: return None if isinstance(value, (int, long)): @@ -32,12 +29,6 @@ class Numeric(Float): assert isinstance(value, Decimal) return value - def sql_type(self): - db_type = backend.name() - if db_type == 'mysql': - return SQLType('DECIMAL', 'DECIMAL(65, 30)') - return SQLType('NUMERIC', 'NUMERIC') - def sql_column(self, table): column = super(Numeric, self).sql_column(table) db_type = backend.name() diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py index 00dbe18bfb..39ac0e8dcc 100644 --- a/trytond/model/fields/one2many.py +++ b/trytond/model/fields/one2many.py @@ -76,6 +76,9 @@ class One2Many(Field): size = property(_get_size, _set_size) + def sql_type(self): + return None + @property def filter(self): return self.__filter diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py index cd94911314..0e56cd666f 100644 --- a/trytond/model/fields/reference.py +++ b/trytond/model/fields/reference.py @@ -5,11 +5,9 @@ import warnings from sql import Cast, Literal, Query, Expression from sql.functions import Substring, Position -from .field import Field, SQLType -from .char import Char +from .field import Field from ...transaction import Transaction from ...pool import Pool -from ... import backend from ...rpc import RPC @@ -18,6 +16,7 @@ class Reference(Field): Define a reference field (``str``). ''' _type = 'reference' + _sql_type = 'VARCHAR' def __init__(self, string='', selection=None, selection_change_with=None, help='', required=False, readonly=False, domain=None, states=None, @@ -122,20 +121,13 @@ class Reference(Field): value = Target(value) super(Reference, self).__set__(inst, value) - @staticmethod - def sql_format(value): + def sql_format(self, value): if not isinstance(value, (basestring, Query, Expression)): try: value = '%s,%s' % tuple(value) except TypeError: pass - return Char.sql_format(value) - - def sql_type(self): - db_type = backend.name() - if db_type == 'mysql': - return SQLType('CHAR', 'VARCHAR(255)') - return SQLType('VARCHAR', 'VARCHAR') + return super(Reference, self).sql_format(value) def convert_domain(self, domain, tables, Model): if '.' not in domain[0]: diff --git a/trytond/model/fields/selection.py b/trytond/model/fields/selection.py index a1bc083dcd..e7fddb0deb 100644 --- a/trytond/model/fields/selection.py +++ b/trytond/model/fields/selection.py @@ -4,10 +4,9 @@ import warnings from sql.conditionals import Case -from ... import backend from ...transaction import Transaction from ...tools import is_instance_method -from .field import Field, SQLType +from .field import Field from ...rpc import RPC @@ -16,6 +15,7 @@ class Selection(Field): Define a selection field (``str``). ''' _type = 'selection' + _sql_type = 'VARCHAR' def __init__(self, selection, string='', sort=True, selection_change_with=None, translate=True, help='', @@ -55,12 +55,6 @@ class Selection(Field): model.__rpc__.setdefault( self.selection, RPC(instantiate=instantiate)) - def sql_type(self): - db_type = backend.name() - if db_type == 'mysql': - return SQLType('CHAR', 'VARCHAR(255)') - return SQLType('VARCHAR', 'VARCHAR') - def convert_order(self, name, tables, Model): if getattr(Model, 'order_%s' % name, None): return super(Selection, self).convert_order(name, tables, Model) diff --git a/trytond/model/fields/text.py b/trytond/model/fields/text.py index f97a805dd9..49ee59554f 100644 --- a/trytond/model/fields/text.py +++ b/trytond/model/fields/text.py @@ -1,7 +1,5 @@ # 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 .field import SQLType from .char import Char @@ -10,6 +8,4 @@ class Text(Char): Define a text field (``unicode``). ''' _type = 'text' - - def sql_type(self): - return SQLType('TEXT', 'TEXT') + _sql_type = 'TEXT'