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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <topics-backend_types>`.
|
||||
|
||||
.. method:: Field.sql_column(table)
|
||||
|
||||
Return the Column instance based on table.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user