Merge lp:~openerp-dev/openobject-server/trunk-smart-fields-pgtype-xmo into lp:openobject-server

Proposed by Xavier (Open ERP)
Status: Needs review
Proposed branch: lp:~openerp-dev/openobject-server/trunk-smart-fields-pgtype-xmo
Merge into: lp:openobject-server
Diff against target: 325 lines (+85/-79)
2 files modified
openerp/osv/fields.py (+75/-4)
openerp/osv/orm.py (+10/-75)
To merge this branch: bzr merge lp:~openerp-dev/openobject-server/trunk-smart-fields-pgtype-xmo
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+109134@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

4189. By Xavier (Open ERP)

[IMP] move conversion from OpenERP fields to Postgres types from the ORM to the fields

* Cleaner
* Fields are more self-contained (can understand mapping from reading the field)
* Simpler to create new field types (WIP)
* Removes a bunch of code from orm

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openerp/osv/fields.py'
2--- openerp/osv/fields.py 2012-05-21 07:38:51 +0000
3+++ openerp/osv/fields.py 2012-06-07 12:55:24 +0000
4@@ -78,6 +78,9 @@
5 # used to hide a certain field type in the list of field types
6 _deprecated = False
7
8+ # Postgres data type for the column, returned by pg_type unless overwritten
9+ _pg_type = None
10+
11 def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
12 """
13
14@@ -127,6 +130,37 @@
15 res = obj.read(cr, uid, ids, [name], context=context)
16 return [x[name] for x in res]
17
18+ @property
19+ def pg_type(self):
20+ """ Postgres column types for the OpenERP field, fully qualified (e.g.
21+ including size for char columns with one)
22+
23+ :returns: tuple
24+ :rtype: (str, str)
25+ """
26+ return self.pg_type_for(self)
27+
28+ @classmethod
29+ def pg_type_for(cls, field):
30+ """ Method actually fetching the postgres type for a field. Has to be
31+ a class method in case a column type A needs to delegate to a column
32+ type B (e.g. function fields)
33+
34+ By default, simply returns the value of the _pg_type class attribute.
35+
36+ :param cls: current column object
37+ :param field: current column instance
38+ :returns: a pair of (type, type)
39+ :rtype: (str, str)
40+ """
41+ return cls._pg_type
42+
43+class _varchar(_column):
44+ @classmethod
45+ def pg_type_for(cls, field):
46+ if not field.size:
47+ return 'varchar', 'varchar'
48+ return 'varchar', 'varchar(%d)' % field.size
49
50 # ---------------------------------------------------------
51 # Simple fields
52@@ -137,6 +171,8 @@
53 _symbol_f = lambda x: x and 'True' or 'False'
54 _symbol_set = (_symbol_c, _symbol_f)
55
56+ _pg_type = ('bool', 'bool')
57+
58 def __init__(self, string='unknown', required=False, **args):
59 super(boolean, self).__init__(string=string, required=required, **args)
60 if required:
61@@ -152,6 +188,8 @@
62 _symbol_set = (_symbol_c, _symbol_f)
63 _symbol_get = lambda self,x: x or 0
64
65+ _pg_type = ('int4', 'int4')
66+
67 def __init__(self, string='unknown', required=False, **args):
68 super(integer, self).__init__(string=string, required=required, **args)
69 if required:
70@@ -160,7 +198,7 @@
71 " `required` has no effect, as NULL values are "
72 "automatically turned into 0.")
73
74-class reference(_column):
75+class reference(_varchar):
76 _type = 'reference'
77 _classic_read = False # post-process to handle missing target
78
79@@ -178,7 +216,7 @@
80 result[value['id']] = False
81 return result
82
83-class char(_column):
84+class char(_varchar):
85 _type = 'char'
86
87 def __init__(self, string, size, **args):
88@@ -204,6 +242,8 @@
89 class text(_column):
90 _type = 'text'
91
92+ _pg_type = ('text', 'text')
93+
94 import __builtin__
95
96 class float(_column):
97@@ -213,6 +253,12 @@
98 _symbol_set = (_symbol_c, _symbol_f)
99 _symbol_get = lambda self,x: x or 0.0
100
101+ @classmethod
102+ def pg_type_for(cls, field):
103+ if field.digits:
104+ return 'numeric', 'numeric'
105+ return 'float8', 'double precision'
106+
107 def __init__(self, string='unknown', digits=None, digits_compute=None, required=False, **args):
108 _column.__init__(self, string=string, required=required, **args)
109 self.digits = digits
110@@ -236,6 +282,8 @@
111 class date(_column):
112 _type = 'date'
113
114+ _pg_type = ('date', 'date')
115+
116 @staticmethod
117 def today(*args):
118 """ Returns the current date in a format fit for being a
119@@ -281,6 +329,9 @@
120
121 class datetime(_column):
122 _type = 'datetime'
123+
124+ _pg_type = ('timestamp', 'timestamp')
125+
126 @staticmethod
127 def now(*args):
128 """ Returns the current datetime in a format fit for being a
129@@ -326,6 +377,8 @@
130 _type = 'binary'
131 _symbol_c = '%s'
132
133+ _pg_type = ('bytea', 'bytea')
134+
135 # Binary values may be byte strings (python 2.6 byte array), but
136 # the legacy OpenERP convention is to transfer and store binaries
137 # as base64-encoded strings. The base64 string may be provided as a
138@@ -368,9 +421,17 @@
139 res[i] = val
140 return res
141
142-class selection(_column):
143+class selection(_varchar):
144 _type = 'selection'
145
146+ @classmethod
147+ def pg_type_for(cls, field):
148+ if (isinstance(field.selection, list) and isinstance(field.selection[0][0], int))\
149+ or getattr(field, 'size', None) == -1:
150+ return 'int4', 'int4'
151+
152+ return super(selection, cls).pg_type_for(field)
153+
154 def __init__(self, selection, string='unknown', **args):
155 _column.__init__(self, string=string, **args)
156 self.selection = selection
157@@ -396,6 +457,8 @@
158 _symbol_f = lambda x: x or None
159 _symbol_set = (_symbol_c, _symbol_f)
160
161+ _pg_type = ('int4', 'int4')
162+
163 def __init__(self, obj, string='unknown', **args):
164 _column.__init__(self, string=string, **args)
165 self._obj = obj
166@@ -1078,6 +1141,12 @@
167 if self._fnct_inv:
168 self._fnct_inv(obj, cr, user, id, name, value, self._fnct_inv_arg, context)
169
170+ @classmethod
171+ def pg_type_for(cls, field):
172+ if field._type == 'selection':
173+ return 'varchar', 'varchar'
174+ return globals()[field._type].pg_type_for(field)
175+
176 # ---------------------------------------------------------
177 # Related fields
178 # ---------------------------------------------------------
179@@ -1303,7 +1372,7 @@
180
181 def __init__(self, serialization_field, **kwargs):
182 self.serialization_field = serialization_field
183- return super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
184+ super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
185
186
187
188@@ -1350,6 +1419,8 @@
189 _symbol_set = (_symbol_c, _symbol_f)
190 _symbol_get = _symbol_get_struct
191
192+ _pg_type = ('text', 'text')
193+
194 # TODO: review completly this class for speed improvement
195 class property(function):
196
197
198=== modified file 'openerp/osv/orm.py'
199--- openerp/osv/orm.py 2012-06-06 14:19:08 +0000
200+++ openerp/osv/orm.py 2012-06-07 12:55:24 +0000
201@@ -528,71 +528,6 @@
202 self._cache[model].clear()
203 self._cache[model].update(cached_ids)
204
205-def pg_varchar(size=0):
206- """ Returns the VARCHAR declaration for the provided size:
207-
208- * If no size (or an empty or negative size is provided) return an
209- 'infinite' VARCHAR
210- * Otherwise return a VARCHAR(n)
211-
212- :type int size: varchar size, optional
213- :rtype: str
214- """
215- if size:
216- if not isinstance(size, int):
217- raise TypeError("VARCHAR parameter should be an int, got %s"
218- % type(size))
219- if size > 0:
220- return 'VARCHAR(%d)' % size
221- return 'VARCHAR'
222-
223-FIELDS_TO_PGTYPES = {
224- fields.boolean: 'bool',
225- fields.integer: 'int4',
226- fields.text: 'text',
227- fields.date: 'date',
228- fields.datetime: 'timestamp',
229- fields.binary: 'bytea',
230- fields.many2one: 'int4',
231- fields.serialized: 'text',
232-}
233-
234-def get_pg_type(f, type_override=None):
235- """
236- :param fields._column f: field to get a Postgres type for
237- :param type type_override: use the provided type for dispatching instead of the field's own type
238- :returns: (postgres_identification_type, postgres_type_specification)
239- :rtype: (str, str)
240- """
241- field_type = type_override or type(f)
242-
243- if field_type in FIELDS_TO_PGTYPES:
244- pg_type = (FIELDS_TO_PGTYPES[field_type], FIELDS_TO_PGTYPES[field_type])
245- elif issubclass(field_type, fields.float):
246- if f.digits:
247- pg_type = ('numeric', 'NUMERIC')
248- else:
249- pg_type = ('float8', 'DOUBLE PRECISION')
250- elif issubclass(field_type, (fields.char, fields.reference)):
251- pg_type = ('varchar', pg_varchar(f.size))
252- elif issubclass(field_type, fields.selection):
253- if (isinstance(f.selection, list) and isinstance(f.selection[0][0], int))\
254- or getattr(f, 'size', None) == -1:
255- pg_type = ('int4', 'INTEGER')
256- else:
257- pg_type = ('varchar', pg_varchar(getattr(f, 'size', None)))
258- elif issubclass(field_type, fields.function):
259- if f._type == 'selection':
260- pg_type = ('varchar', pg_varchar())
261- else:
262- pg_type = get_pg_type(f, getattr(fields, f._type))
263- else:
264- _logger.warning('%s type not supported!', field_type)
265- pg_type = None
266-
267- return pg_type
268-
269-
270 class MetaModel(type):
271 """ Metaclass for the Model.
272
273@@ -2906,23 +2841,23 @@
274 self._table, k)
275 f_obj_type = None
276 else:
277- f_obj_type = get_pg_type(f) and get_pg_type(f)[0]
278+ f_obj_type = f.pg_type and f.pg_type[0]
279
280 if f_obj_type:
281 ok = False
282 casts = [
283- ('text', 'char', pg_varchar(f.size), '::%s' % pg_varchar(f.size)),
284+ ('text', 'char', f.pg_type[1], '::%s' % f.pg_type[1]),
285 ('varchar', 'text', 'TEXT', ''),
286- ('int4', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
287+ ('int4', 'float', f.pg_type[1], '::'+f.pg_type[1]),
288 ('date', 'datetime', 'TIMESTAMP', '::TIMESTAMP'),
289 ('timestamp', 'date', 'date', '::date'),
290- ('numeric', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
291- ('float8', 'float', get_pg_type(f)[1], '::'+get_pg_type(f)[1]),
292+ ('numeric', 'float', f.pg_type[1], '::'+f.pg_type[1]),
293+ ('float8', 'float', f.pg_type[1], '::'+f.pg_type[1]),
294 ]
295 if f_pg_type == 'varchar' and f._type == 'char' and f_pg_size < f.size:
296 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k))
297- cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, pg_varchar(f.size)))
298- cr.execute('UPDATE "%s" SET "%s"=temp_change_size::%s' % (self._table, k, pg_varchar(f.size)))
299+ cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, f.pg_type[1]))
300+ cr.execute('UPDATE "%s" SET "%s"=temp_change_size::%s' % (self._table, k, f.pg_type[1]))
301 cr.execute('ALTER TABLE "%s" DROP COLUMN temp_change_size CASCADE' % (self._table,))
302 cr.commit()
303 _schema.debug("Table '%s': column '%s' (type varchar) changed size from %s to %s",
304@@ -2955,7 +2890,7 @@
305 if f_pg_notnull:
306 cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL' % (self._table, k))
307 cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (self._table, k, newname))
308- cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
309+ cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, f.pg_type[1]))
310 cr.execute("COMMENT ON COLUMN %s.\"%s\" IS %%s" % (self._table, k), (f.string,))
311 _schema.debug("Table '%s': column '%s' has changed type (DB=%s, def=%s), data moved to column %s !",
312 self._table, k, f_pg_type, f._type, newname)
313@@ -3020,10 +2955,10 @@
314 else:
315 if not isinstance(f, fields.function) or f.store:
316 # add the missing field
317- cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, get_pg_type(f)[1]))
318+ cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, f.pg_type[1]))
319 cr.execute("COMMENT ON COLUMN %s.\"%s\" IS %%s" % (self._table, k), (f.string,))
320 _schema.debug("Table '%s': added column '%s' with definition=%s",
321- self._table, k, get_pg_type(f)[1])
322+ self._table, k, f.pg_type[1])
323
324 # initialize it
325 if not create and k in self._defaults: