Changeset 43
- Timestamp:
- 12/27/04 07:17:49
- Files:
-
- trunk/__init__.py (modified) (1 diff)
- trunk/doc/framework.html (modified) (3 diffs)
- trunk/doc/index.html (modified) (1 diff)
- trunk/doc/modeling.html (modified) (1 diff)
- trunk/doc/storage.html (modified) (1 diff)
- trunk/engines.py (modified) (1 diff)
- trunk/storage/db.py (added)
- trunk/storage/storeado.py (modified) (40 diffs)
- trunk/storage/storemysql.py (added)
- trunk/storage/storeodbc.py (modified) (1 diff)
- trunk/storage/storepypgsql.py (modified) (19 diffs)
- trunk/storage/test_storeado.py (modified) (6 diffs)
- trunk/storage/test_storemysql.py (added)
- trunk/storage/test_storepypgsql.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/__init__.py
r42 r43 175 175 is to inform Managers that would usually store unicode strings 176 176 as strings of length 255, that a particular value should be 177 a larger object; this is done with a ' Size' mapping, such as:178 hints = {u' Size': 0}, where 0 implies no limit. Canonical storage179 hint names and implementation details may be found in storage.py177 a larger object; this is done with a 'bytes' mapping, such as: 178 hints = {u'bytes': 0}, where 0 implies no limit. Canonical storage 179 hint names and implementation details may be found in /storage 180 180 documentation. 181 181 """ trunk/doc/framework.html
r42 r43 86 86 <ol> 87 87 <li>Adapters, which coerce values from Python types to database types 88 and back again.</li> 88 and back again. Base classes for DB Adapters can be found in 89 <tt>dejavu.storage.db</tt>.</li> 89 90 <li>An SQLDecompiler, which converts dejavu <tt>Expression</tt> objects 90 91 (essentially, Python lambdas) into SQL.</li> … … 97 98 <p>Generally, you will end up with three kinds of Adapters (subclasses of 98 99 <tt>storage.Adapter</tt>): one for converting Dejavu types to your database 99 types, another for the reverse, and probably a third to insert Dejavu 100 values (with proper quoting, etc.) into SQL statements for your database. 100 types, another for the reverse (<tt>storage.db.AdapterFromDB</tt>), and 101 probably a third to insert Dejavu values (with proper quoting, etc.) into 102 SQL statements for your database (<tt>storage.db.AdapterToSQL</tt>). 101 103 The Adapter class provides a single public method, <tt>coerce(self, value, 102 104 valuetype=None)</tt>, which takes any value and attempts to return a new … … 110 112 <pre> def coerce_int(self, value): 111 113 return str(value)</pre> 112 Methods are named <tt>coerce_</tt>, plus the Python type name. </p>113 114 <p>Your coercion method should receive a single value and 115 return that value, coerced to a type. <i>The same signature and process 116 hold whether you are coercing inbound or outbound data.</i> An outbound 117 adapter coerces from Dejavu types to database types. You supply a Dejavu 118 UnitProperty value to <tt>coerce</tt>, and the appropriate coercion method 119 will be selected based upon the <tt>type()</tt> of that value. An inbound 120 adapter, on the other hand, coerces from DB types to Dejavu types. Call 121 <tt>coerce</tt> with your database value <i>and</i> the <tt>valuetype</tt> 122 argument, which is then used to call the appropriate coercion method. That 123 method returns the value, coerced to <tt>type(valuetype)</tt>, which the 124 UnitProperty expects. In both cases, the name of each coercion method takes 125 the Dejavu/Python type name, not thedatabase type name.</p>114 Methods are named <tt>coerce_</tt>, plus the Python type name. Again, you 115 can most likely use methods in the base Adapter classes provided.</p> 116 117 <p>Your coercion method should receive a single value and return that value, 118 coerced to a type. An outbound adapter coerces from Dejavu types to database 119 types. You supply a Dejavu UnitProperty value to <tt>coerce</tt>, and the 120 appropriate coercion method will be selected based upon the <tt>type()</tt> 121 of that value. An inbound adapter, on the other hand, coerces from DB types 122 to Dejavu types. Call <tt>coerce</tt> with your database value <i>and</i> 123 the <tt>valuetype</tt> argument, which is then used to call the appropriate 124 coercion method. That method returns the value, coerced to 125 <tt>type(valuetype)</tt>, which the UnitProperty expects. In both cases, 126 the name of each coercion method takes the Dejavu/Python type name, not the 127 database type name.</p> 126 128 127 129 <p>If <tt>coerce</tt> cannot find a method for the appropriate Python type, trunk/doc/index.html
r42 r43 89 89 <li>Microsoft Access (Jet)</li> 90 90 <li>Microsoft SQL Server</li> 91 <li>PostgreSQL</li> 92 <li>MySQL</li> 91 93 <li>ODBC</li> 92 94 <li>Shelve</li> trunk/doc/modeling.html
r42 r43 128 128 for example, is to inform Managers that would usually store unicode strings 129 129 as strings of length 255, that a particular value should be a larger object; 130 this is done with a ' Size' mapping, such as <tt>hints = {u'Size': 0}</tt>,130 this is done with a 'bytes' mapping, such as <tt>hints = {u'bytes': 0}</tt>, 131 131 where 0 implies no limit.</p> 132 132 trunk/doc/storage.html
r42 r43 125 125 </ul> 126 126 127 <h5>PostgreSQL (pyPgSQL)</h5> 128 <p>This class was developed against PostgreSQL 8.0.0 rc-1. Configuration entries:</p> 129 <ul> 130 <li><b>Class:</b> <tt>dejavu.storage.storepypgsql.StorageManagerPgSQL</tt></li> 131 <li><b>Connect:</b> A connect string of the form "k=v k=v". For example, 132 <tt>"host=localhost dbname=myapp user=postgres password=hilar1ous"</tt>. 133 See the <a href='http://www.postgresql.org/docs/current/static/libpq.html'>libpq</a> 134 docs for complete information.</li> 135 <li><b>Threaded:</b> Optional. If set to any value, a new connection 136 is established for each thread. Otherwise, a single connection 137 is used for all threads.</li> 138 <li><b>Prefix:</b> If specified, all tables in the database will 139 have names starting with this prefix. If not provided, it 140 defaults to "djv". This helps if you need to mix Dejavu tables 141 with tables from another application.</li> 142 <li><b>Create If Missing:</b> If not blank or missing, create the database 143 as needed.</p> 144 </ul> 145 146 <h5>MySQL (MySQLdb)</h5> 147 <p>This class was developed against: 148 mysql Ver 14.7 Distrib 4.1.8, for Win95/Win98 (i32). 149 Configuration entries:</p> 150 <ul> 151 <li><b>Class:</b> <tt>dejavu.storage.storemysql.StorageManagerMySQL</tt></li> 152 <li>Connection arguments: any of "host", "user", "passwd", "db", "port", 153 "unix_socket", "client_flag".<br />See the 154 <a href='http://dev.mysql.com/doc/mysql/en/mysql_real_connect.html'>docs</a> 155 for complete info.</li> 156 <li><b>Threaded:</b> Optional. If set to any value, a new connection 157 is established for each thread. Otherwise, a single connection 158 is used for all threads.</li> 159 <li><b>Prefix:</b> If specified, all tables in the database will 160 have names starting with this prefix. If not provided, it 161 defaults to "djv". This helps if you need to mix Dejavu tables 162 with tables from another application.</li> 163 <li><b>Create If Missing:</b> If not blank or missing, create the database 164 as needed.</p> 165 </ul> 166 127 167 <h5>ODBC</h5> 128 168 <p>This class doesn't currently work. It needs some updating. Configuration trunk/engines.py
r42 r43 159 159 Operation = RuleProperty(str) 160 160 SetID = RuleProperty(int) 161 Operand = RuleProperty(str, False, hints = {u' Size': 0})161 Operand = RuleProperty(str, False, hints = {u'bytes': 0}) 162 162 Sequence = RuleProperty(int) 163 163 EngineID = dejavu.UnitProperty(int, index=True) trunk/storage/storeado.py
r42 r43 21 21 import dejavu 22 22 from dejavu import storage, codewalk, logic 23 from dejavu.storage import db 23 24 import recur 24 25 … … 32 33 adLockOptimistic = 3 33 34 adLockBatchOptimistic = 4 35 36 adUseClient = 3 34 37 35 38 adModeShareExclusive = 12 … … 56 59 57 60 58 class AdapterFromADO(storage.Adapter): 61 class AdapterToADOSQL(db.AdapterToSQL): 62 """Coerce Expression constants to ADO SQL.""" 63 64 escapes = [("'", "''"), ("%", "[%]"), ("_", "[_]")] 65 66 def coerce_datetime_datetime(self, value): 67 return (u'#%s/%s/%s %02d:%02d:%02d#' % 68 (value.month, value.day, value.year, 69 value.hour, value.minute, value.second)) 70 71 def coerce_datetime_date(self, value): 72 return u'#%s/%s/%s#' % (value.month, value.day, value.year) 73 74 def coerce_datetime_time(self, value): 75 return u'#%02d:%02d:%02d#' % (value.hour, value.minute, value.second) 76 77 78 class AdapterFromADO(db.AdapterFromDB): 59 79 """Coerce incoming values from ADO to Dejavu datatypes.""" 60 80 61 def __init__(self, unit=None): 62 self.unit = unit 63 64 def consume(self, key, value): 65 expectedType = self.unit.__class__.property_type(key) 66 value = self.coerce(value, expectedType) 67 # Set the attribute directly to avoid __set__ overhead. 68 self.unit._properties[key] = value 69 70 def pickle(self, value): 71 aType, value = value 72 if value is None: 73 return None 74 75 # Coerce to str for pickle.loads restriction. 76 value = str(value) 77 return pickle.loads(value) 78 79 def coerce_datetime_datetime(self, value): 81 def coerce_datetime_datetime(self, value, coltype): 80 82 # Illegal Date/Time values will crash the 81 83 # app when using value.Format(). Therefore, 82 84 # grab the value and figure the date ourselves. 83 85 # Use 1-second resolution only. 84 aType, value = value 85 if value is None: 86 return None 87 elif isinstance(value, basestring): 86 if isinstance(value, basestring): 88 87 return datetime.datetime(int(value[0:4]), int(value[4:6]), 89 88 int(value[6:8])) … … 94 93 return datetime.datetime.combine(aDate, aTime) 95 94 96 def coerce_datetime_date(self, value ):95 def coerce_datetime_date(self, value, coltype): 97 96 # See coerce_datetime 98 aType, value = value 99 if value is None: 100 return None 101 elif isinstance(value, basestring): 97 if isinstance(value, basestring): 102 98 return datetime.date(int(value[0:4]), int(value[4:6]), 103 99 int(value[6:8])) … … 105 101 return datetime.date.fromordinal(int(float(value)) + zeroHour) 106 102 107 def coerce_datetime_time(self, value ):103 def coerce_datetime_time(self, value, coltype): 108 104 # See coerce_datetime 109 aType, value = value 110 if value is None: 111 return None 112 else: 113 day, aTime = time_from_com(value) 114 return aTime 115 116 def coerce_datetime_timedelta(self, value): 117 aType, value = value 118 if value is None: 119 return None 120 121 days, seconds = divmod(value, 1) 122 return datetime.timedelta(days, int(seconds * 86400)) 123 124 coerce_dict = pickle 125 126 def coerce_fixedpoint_FixedPoint(self, value): 127 aType, value = value 128 if value is None: 129 return None 130 if aType == 0x06: 105 day, aTime = time_from_com(value) 106 return aTime 107 108 def coerce_fixedpoint_FixedPoint(self, value, coltype): 109 if coltype == 0x06: 131 110 # Currency 132 111 value = value[1] / 10000.0 133 112 return fixedpoint.FixedPoint(value) 134 113 135 def coerce_float(self, value): 136 aType, value = value 137 if value is None: 138 return None 139 if aType == 0x06: 114 def coerce_float(self, value, coltype): 115 if coltype == 0x06: 140 116 # Currency 141 117 value = value[1] / 10000.0 142 118 return float(value) 143 119 144 def coerce_int(self, value): 145 aType, value = value 146 if value is None: 147 return None 148 if aType == 0x0b: 120 def coerce_int(self, value, coltype): 121 if coltype == 0x0b: 149 122 # Boolean 150 123 return value != 0 … … 152 125 153 126 coerce_bool = coerce_int 154 coerce_list = pickle 155 156 def coerce_long(self, value): 157 aType, value = value 158 if value is None: 159 return None 160 return long(value) 161 162 def coerce_str(self, value): 163 aType, value = value 164 if value is None: 165 return None 166 return str(value) 167 168 coerce_tuple = pickle 169 170 def coerce_unicode(self, value): 171 aType, value = value 172 if value is None: 173 return None 127 128 def coerce_unicode(self, value, coltype): 174 129 if isinstance(value, unicode): 175 130 # For some reason, inValue is already a unicode object. … … 236 191 237 192 238 class AdapterToADOSQL(storage.Adapter):239 """Coerce Expression constants to ADO SQL."""240 241 def tostr(self, value):242 return str(value)243 244 def coerce_NoneType(self, value):245 return "Null"246 247 def coerce_bool(self, value):248 if value:249 return 'True'250 return 'False'251 252 def coerce_datetime_datetime(self, value):253 return (u'#%s/%s/%s %02d:%02d:%02d#' %254 (value.month, value.day, value.year,255 value.hour, value.minute, value.second))256 257 def coerce_datetime_date(self, value):258 return u'#%s/%s/%s#' % (value.month, value.day, value.year)259 260 def coerce_datetime_time(self, value):261 return u'#%02d:%02d:%02d#' % (value.hour, value.minute, value.second)262 263 def coerce_datetime_timedelta(self, value):264 float_val = value.days + (value.seconds / 86400.0)265 return repr(float_val)266 267 coerce_fixedpoint_FixedPoint = tostr268 coerce_float = tostr269 coerce_int = tostr270 271 def coerce_list(self, value):272 return "(" + ", ".join([self.coerce(x) for x in value]) + ")"273 274 coerce_long = tostr275 276 def coerce_str(self, value):277 value = value.replace(u"'", u"''")278 value = value.replace("%", "[%]")279 value = value.replace("_", "[_]")280 return "'" + value + "'"281 282 coerce_tuple = coerce_list283 284 coerce_unicode = coerce_str285 286 287 193 def icontainedby(op1, op2, notin=False): 288 # This test doesn't work right, now that we use lists as 289 # well as tuples with IN. Need a way to mark field refs. 290 if op2.startswith("[") and op2.endswith("]"): 194 if isinstance(op1, db.ConstWrapper): 291 195 # Looking for text in a field. Use Like (reverse terms). 196 # LIKE is case-insensitive in MS SQL Server. 292 197 value = op2 + " Like '%" + op1[1:-1] + "%'" 293 198 else: 294 199 # Looking for field in (a, b, c) 295 value = op1 + " in " + op2 200 value = op1 + " in (" + ", ".join([AdapterToADOSQL().coerce(x) 201 for x in op2.basevalue]) + ")" 296 202 if notin: 297 203 value = "not " + value … … 324 230 dejavu.today: lambda: "DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)", 325 231 dejavu.year: lambda x: "YEAR(" + x + ")", 232 len: lambda x: "Len(" + x + ")", 326 233 } 327 234 … … 338 245 result = self.stack[0] 339 246 if result is cannot_represent: 340 result = 'T rue'247 result = 'TRUE' 341 248 return result, self.imperfect 342 249 … … 347 254 term, operation = terms.pop() 348 255 # All this checking of cannot_represent is done so that a 349 # function (like dejavu.iscurrentweek) can be labeled350 # imperfect--all Units (which match the rest of the351 # Expression) will be recalled. They can then be352 # compared in expr.evaluate(unit).256 # function can be labeled imperfect. For example, the function 257 # dejavu.iscurrentweek has no ADO SQL equivalent. All Units 258 # (which match the rest of the Expression) will be recalled. 259 # They can then be compared in expr.evaluate(unit). 353 260 if term is not cannot_represent: 354 261 if comp is cannot_represent: … … 382 289 # Some constants are function or class objects, 383 290 # which should not be coerced. 384 no_coerce = (FunctionType, type) 385 if isinstance(val, no_coerce): 386 pass 387 elif isinstance(val, type(len)): 388 val = str(val) 389 else: 390 val = self.adapter.coerce(val) 291 no_coerce = (FunctionType, type, 292 type(len), # <type 'builtin_function_or_method'> 293 ) 294 if not isinstance(val, no_coerce): 295 val = db.ConstWrapper(val, self.adapter.coerce(val)) 391 296 self.stack.append(val) 392 297 … … 430 335 self.imperfect = True 431 336 return 432 elif isinstance(func, basestring) and func == '<built-in function len>':433 self.stack.append("Len(" + args[0] + ")")434 return435 337 436 338 if self.stack: … … 442 344 def visit_COMPARE_OP(self, lo, hi): 443 345 op2, op1 = self.stack.pop(), self.stack.pop() 444 op = self.sql_cmp_op[lo + (hi << 8)] 445 if op == 'in': 446 self.stack.append(icontainedby(op1, op2)) 346 op = lo + (hi << 8) 347 if op in (6, 7): # in, not in 348 if isinstance(op1, db.ConstWrapper): 349 # Looking for text in a field. Use Like (reverse terms). 350 # LIKE is case-insensitive in MS SQL Server (and there 351 # doesn't seem to be a way around it). Mark imperfect, 352 # but use the imperfect, insensitive filter at least. 353 value = op2 + " Like '%" + op1[1:-1] + "%'" 354 else: 355 # Looking for field in (a, b, c) 356 value = op1 + " in (" + ", ".join([AdapterToADOSQL().coerce(x) 357 for x in op2.basevalue]) + ")" 358 if op == 7: 359 value = "not " + value 360 self.stack.append(value) 447 361 self.imperfect = True 448 elif op == 'not in': 449 self.stack.append(icontainedby(op1, op2, True)) 450 self.imperfect = True 451 elif op == '=' and op2 == 'Null': 452 self.stack.append(op1 + " Is Null") 453 elif op == '=' and op1 == 'Null': 454 self.stack.append(op2 + " Is Null") 455 elif op == '!=' and op2 == 'Null': 456 self.stack.append(op1 + " Is Not Null") 457 elif op == '!=' and op1 == 'Null': 458 self.stack.append(op2 + " Is Not Null") 459 else: 460 if op2.startswith("'") and op2.endswith("'"): 362 elif op1 == 'NULL': 363 if op == 2: 364 self.stack.append(op2 + " IS NULL") 365 elif op == 3: 366 self.stack.append(op2 + " IS NOT NULL") 367 else: 368 raise ValueError("Non-equality Null comparisons not allowed.") 369 elif op2 == 'NULL': 370 if op == 2: 371 self.stack.append(op1 + " IS NULL") 372 elif op == 3: 373 self.stack.append(op1 + " IS NOT NULL") 374 else: 375 raise ValueError("Non-equality Null comparisons not allowed.") 376 else: 377 if (isinstance(op2, db.ConstWrapper) 378 and isinstance(op2.basevalue, basestring)): 461 379 # ADO comparison operators for strings are case-insensitive 462 380 # by default. Rather than determine which columns in the DB 463 381 # might be case-sensitive, just flag them all as imperfect. 382 # TODO: might be possible to cast both to varbinary, but 383 # that may cause problems with unicode columns. 464 384 self.imperfect = True 465 self.stack.append(op1 + " " + op+ " " + op2)385 self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 466 386 467 387 def binary_op(self, op): … … 471 391 def visit_BINARY_SUBSCR(self): 472 392 # The only BINARY_SUBSCR used in Expressions should be kwargs[key]. 393 # TODO: provide string slicing? 473 394 name = self.stack.pop() 474 395 tos = self.stack.pop() … … 484 405 op = self.stack.pop() 485 406 if op is cannot_represent: 486 # Usually as a result of has(farClassName).487 407 self.stack.append(cannot_represent) 488 408 else: 489 409 self.stack.append("not (" + op + ")") 490 491 492 def safe_name(content):493 return unicode(content).replace(u"_", u"")494 410 495 411 … … 506 422 self.sql, self.imperfect = store.select(unitClass, expr) 507 423 508 def field(self, key, row):509 try:510 col = self.colIndices[key]511 except KeyError, x:512 x.args += (key, self.unitClass.__name__)513 raise x514 515 return (self.fieldTypes[col], self.data[col][row])516 517 424 def load_data(self): 518 425 anRS = self.store.recordset(self.sql, adOpenForwardOnly, … … 525 432 self.data = [] 526 433 if not(anRS.BOF and anRS.EOF): 527 ## anRS.MoveFirst()528 ## if not(anRS.BOF or anRS.EOF):529 434 # We tried .MoveNext() and lots of Fields.Item() calls. 530 435 # Using GetRows() beats that time by about 2/3. … … 535 440 s = self.store 536 441 clsname = self.unitClass.__name__ 537 tbl = "%s_%s" % (s.prefix, safe_name(clsname))538 442 self.load_data() 539 443 if len(self.data) > 0: … … 545 449 # Grab the expanded data 546 450 try: 547 rs = s.recordset(u"SELECT EXPVAL FROM [%s_%s_%s]" 548 % (tbl, 549 safe_name(self.field('ID', row)[1]), 550 safe_name(key))) 451 col = self.colIndices['ID'] 452 ID = self.data[col][row] 453 rs = s.recordset(u"SELECT EXPVAL FROM %s" % 454 s.identifier(s.prefix, "_", 455 clsname, "_", 456 ID, "_", key)) 551 457 except pywintypes.com_error, x: 552 458 # This usually occurs because the parent Unit … … 566 472 unit._properties[key] = values 567 473 else: 568 value = self.field(key, row) 569 coercer.consume(key, value) 474 try: 475 col = self.colIndices[key] 476 except KeyError, x: 477 x.args += (key, self.unitClass.__name__) 478 raise x 479 coercer.consume(key, self.data[col][row], 480 self.fieldTypes[col]) 570 481 # If our SQL is imperfect, don't yield it to the 571 482 # caller unless it passes evaluate(). … … 598 509 raise x 599 510 else: 600 coercer.consume(key, (self.fieldTypes[col], 601 self.data[col][row])) 511 coercer.consume(key, self.data[col][row], self.fieldTypes[col]) 602 512 603 513 def load_data(self): … … 610 520 self.data = [] 611 521 if not(anRS.BOF and anRS.EOF): 612 ## anRS.MoveFirst()613 ## if not(anRS.BOF or anRS.EOF):614 522 # We tried .MoveNext() and lots of Fields.Item() calls. 615 523 # Using GetRows() beats that time by about 2/3. … … 636 544 637 545 638 class FieldTypeAdapter(object):639 """Return the SQL typename of a DB column."""640 641 def coerce(self, cls, key):642 """coerce(cls, key) -> SQL typename for valuetype."""643 valuetype = cls.property_type(key)644 mod = valuetype.__module__645 if mod == "__builtin__":646 xform = "coerce_%s" % valuetype.__name__647 else:648 xform = "coerce_%s_%s" % (mod, valuetype.__name__)649 xform = xform.replace(".", "_")650 try:651 xform = getattr(self, xform)652 except AttributeError:653 raise TypeError("'%s' is not handled by %s." %654 (valuetype, self.__class__))655 return xform(cls, key)656 657 def _create_str_storage(self, cls, key):658 """This basic string handler does not know anything about the size659 limitations of the particular database. You should use one of the660 subclasses for your particular database if you need storage for661 strings over 255 characters.662 """663 prop = getattr(cls, key)664 size = int(prop.hints.get(u'Size', '255'))665 return u"VARCHAR(%s)" % size666 667 def coerce_bool(self, cls, key): return u"BIT"668 669 def coerce_datetime_datetime(self, cls, key): return u"TIMESTAMP"670 def coerce_datetime_date(self, cls, key): return u"DATE"671 def coerce_datetime_time(self, cls, key): return u"TIME"672 673 coerce_dict = _create_str_storage674 675 def coerce_fixedpoint_FixedPoint(self, cls, key): return u"FLOAT"676 def coerce_float(self, cls, key): return u"FLOAT"677 def coerce_int(self, cls, key): return u"INTEGER"678 679 coerce_list = _create_str_storage680 coerce_str = _create_str_storage681 coerce_tuple = _create_str_storage682 coerce_unicode = _create_str_storage683 684 685 546 class StorageManagerADO(storage.StorageManager): 686 547 """StoreManager to save and retrieve Units via ADO 2.7. … … 690 551 691 552 decompiler = ADOSQLDecompiler 692 createAdapter = FieldTypeAdapter()553 createAdapter = db.FieldTypeAdapter() 693 554 threaded = False 694 555 … … 728 589 729 590 self.reserve_lock = threading.Lock() 591 592 def identifier(self, *atoms): 593 ident = "[" + ''.join(map(str, atoms)) + "]" 594 return ident 730 595 731 596 def shutdown(self): … … 753 618 def recordset(self, aQuery, cursorType=None, lockType=None): 754 619 anRS = win32com.client.Dispatch(r'ADODB.Recordset') 755 ## anRS.Cursorlocation = 3 # adUseClient; Use to obtain .Recordcount 620 # Uncomment the following line to obtain .Recordcount 621 # anRS.Cursorlocation = adUseClient 756 622 if cursorType is None: 757 623 cursorType = self.cursorType … … 786 652 } 787 653 if len(spath) == 1: 788 params[u'child'] = u"[%(prefix)s%(right)s]" % params654 params[u'child'] = self.identifier(params['prefix'], params['right']) 789 655 else: 790 656 params[u'child'] = u"(%s)" % self._join(spath) … … 807 673 w, new_imp = self.where(cls, expr) 808 674 imp |= new_imp 809 if w and w != "T rue":675 if w and w != "TRUE": 810 676 w = " WHERE %s AND %s" % (w, firstwhere) 811 677 else: … … 813 679 814 680 cols += [(cls, k) for k in cls.properties()] 815 colnames = ["[%s%s].[%s]" % (self.prefix, colcls.__name__, k) 681 colnames = ["%s.%s" % (self.identifier(self.prefix, colcls.__name__), 682 self.identifier(k)) 816 683 for colcls, k in cols] 817 684 … … 821 688 822 689 def select(self, unitClass, expr, distinct_fields=None): 823 tablename = self. prefix + safe_name(unitClass.__name__)690 tablename = self.identifier(self.prefix, unitClass.__name__) 824 691 if distinct_fields: 825 distinct_fields = [ u'[%s]' % xfor x in distinct_fields]826 sql = (u"SELECT DISTINCT %s FROM [%s]" %692 distinct_fields = [self.identifier(x) for x in distinct_fields] 693 sql = (u"SELECT DISTINCT %s FROM %s" % 827 694 (u', '.join(distinct_fields), tablename)) 828 695 else: 829 sql = u"SELECT * FROM [%s]" % tablename696 sql = u"SELECT * FROM %s" % tablename 830 697 w, i = self.where(unitClass, expr) 831 698 if len(w) > 0: … … 864 731 data = [] 865 732 clsname = unit.__class__.__name__ 866 anRS = self.recordset(u"SELECT ID FROM [%s%s];" %867 (self.prefix, safe_name(clsname)))733 anRS = self.recordset(u"SELECT ID FROM %s;" % 734 self.identifier(self.prefix, clsname)) 868 735 if not (anRS.BOF and anRS.EOF): 869 736 data = anRS.GetRows()[0] … … 888 755 clsname = cls.__name__ 889 756 # Use a cursor always--makes mixed-quotes, newline, etc easier. 890 anRS = self.recordset("SELECT * FROM [%s%s]WHERE ID = %s" %891 (self. prefix, safe_name(clsname),757 anRS = self.recordset("SELECT * FROM %s WHERE ID = %s" % 758 (self.identifier(self.prefix, clsname), 892 759 AdapterToADOSQL().coerce(unit.ID))) 893 760 if anRS.EOF and anRS.BOF: … … 918 785 """Save a field using a table specifically for that purpose.""" 919 786 unitcls = unit.__class__ 920 table = ("%s_%s_%s_%s" % (self.prefix, safe_name(unitcls.__name__),921 safe_name(unit.ID), safe_name(key)))787 table = self.identifier(self.prefix, "_", unitcls.__name__, "_", 788 unit.ID, "_", key) 922 789 923 790 conn = self.connection() … … 925 792 # Just drop the old table and start with a new one. 926 793 try: 927 self.execute((u"DROP TABLE [%s];" % table), conn)794 self.execute((u"DROP TABLE %s;" % table), conn) 928 795 except pywintypes.com_error, x: 929 796 pass … … 931 798 # Ugly, ugly hack to get NTEXT or MEMO as appropriate. The point 932 799 # is, we want a large text field so we can pickle each item. 933 ftype = self.createAdapter.coerce_list( None, None)934 self.execute(u"CREATE TABLE [%s](EXPVAL %s);" % (table, ftype), conn)935 936 ins = u"INSERT INTO [" + table + "](EXPVAL) VALUES ('%s');"800 ftype = self.createAdapter.coerce_list(unitcls, key) 801 self.execute(u"CREATE TABLE %s (EXPVAL %s);" % (table, ftype), conn) 802 803 ins = u"INSERT INTO " + table + " (EXPVAL) VALUES ('%s');" 937 804 val = getattr(unit, key) 938 805 if val: … … 946 813 """Delete the unit.""" 947 814 # Use a DELETE command instead of a cursor for better performance. 948 deleteStatement = (u"DELETE * FROM [%s%s]WHERE ID = %s" %949 (self. prefix, safe_name(unit.__class__.__name__),815 deleteStatement = (u"DELETE * FROM %s WHERE ID = %s" % 816 (self.identifier(self.prefix, unit.__class__.__name__), 950 817 AdapterToADOSQL().coerce(unit.ID))) 951 818 self.execute(deleteStatement) 952 819 953 820 def create_storage(self, unitClass): 954 clsname = safe_name(unitClass.__name__)955 956 821 coerce = self.createAdapter.coerce 957 822 fields = [] 958 823 for key in unitClass.properties(): 959 824 if (unitClass.__name__, key) not in self.expanded_columns: 960 fields.append(u"[%s] %s" % (key, coerce(unitClass, key))) 961 self.execute(u"CREATE TABLE [%s%s] (%s)" % 962 (self.prefix, clsname, ", ".join(fields))) 825 fields.append(u"%s %s" % (self.identifier(key), 826 coerce(unitClass, key))) 827 self.execute(u"CREATE TABLE %s (%s)" % 828 (self.identifier(self.prefix, unitClass.__name__), 829 ", ".join(fields))) 963 830 964 831 for index in unitClass.indices(): 965 self.execute(u"CREATE INDEX [%si%s%s] ON [%s%s] (%s ASC)" 966 % (self.prefix, clsname, safe_name(index), 967 self.prefix, clsname, index)) 832 self.execute(u"CREATE INDEX %s ON %s (%s ASC)" 833 % (self.identifier(self.prefix, "i", 834 unitClass.__name__, index), 835 self.identifier(self.prefix, unitClass.__name__), 836 index)) 968 837 969 838 def distinct(self, cls, fields, expr=None): … … 998 867 expectedType = cls.property_type(field) 999 868 actualType = fieldTypes[col] 1000 coerced_row = [coerce( (actualType, val), expectedType)869 coerced_row = [coerce(val, actualType, expectedType) 1001 870 for val in data[col]] 1002 871 coerced_data.append(coerced_row) … … 1012 881 1013 882 1014 class FieldTypeAdapter_SQLServer(FieldTypeAdapter): 1015 1016 def _create_str_storage(self, cls, key): 883 class FieldTypeAdapter_SQLServer(db.FieldTypeAdapter): 884 885 def coerce_str(self, cls, key): 886 # The bytes hint shall not reflect the usual 4-byte base for varchar. 1017 887 prop = getattr(cls, key) 1018 size = int(prop.hints.get(u'Size', '255')) 1019 if size == 0 or size > 8000: 888 bytes = int(prop.hints.get(u'bytes', '0')) 889 if bytes and bytes < 8000: 890 return u"VARCHAR(%s)" % bytes 891 else: 1020 892 # 8000 *bytes* is the absolute upper limit, based on T_SQL docs 1021 893 # for varchar. If there are further fields defined for the class, … … 1026 898 # bump up to NTEXT (1 gig characters). 1027 899 return u"NTEXT" 1028 return u"VARCHAR(%s)" % size1029 1030 # dict, list, and tuple will all be pickled in AdapterToADO1031 def coerce_dict(self, cls, key): return u"NTEXT"1032 def coerce_list(self, cls, key): return u"NTEXT"1033 coerce_str = _create_str_storage1034 def coerce_tuple(self, cls, key): return u"NTEXT"1035 coerce_unicode = _create_str_storage1036 900 1037 901 … … 1063 927 dejavu.today: lambda: "DateValue(Now())", 1064 928 dejavu.year: lambda x: "Year(" + x + ")", 929 len: lambda x: "Len(" + x + ")", 1065 930 } 1066 931 1067 932 1068 class FieldTypeAdapter_MSAccess(FieldTypeAdapter): 1069 1070 def _create_str_storage(self, cls, key): 933 class FieldTypeAdapter_MSAccess(db.FieldTypeAdapter): 934 935 def coerce_str(self, cls, key): 936 # The bytes hint shall not reflect the usual 4-byte base for varchar. 1071 937 prop = getattr(cls, key) 1072 size = int(prop.hints.get(u'Size', '255')) 1073 if size == 0 or size > 255: 938 bytes = int(prop.hints.get(u'bytes', '0')) 939 if bytes and bytes < 8000: 940 return u"VARCHAR(%s)" % bytes 941 else: 1074 942 # 255 chars is the upper limit for TEXT / VARCHAR in MS Access. 1075 943 # MEMO is 1 gigabyte when set programatically (only 64K when set 1076 944 # in Access UI). But then, 1 GB is the limit for the whole DB. 1077 945 return u"MEMO" 1078 return u"VARCHAR(%s)" % size1079 1080 # dict, list, and tuple will all be pickled in AdapterToADO1081 def coerce_dict(self, cls, key): return u"MEMO"1082 def coerce_list(self, cls, key): return u"MEMO"1083 coerce_str = _create_str_storage1084 def coerce_tuple(self, cls, key): return u"MEMO"1085 coerce_unicode = _create_str_storage1086 946 1087 947 trunk/storage/storeodbc.py
r42 r43 471 471 subclasses for your particular database if you need storage for 472 472 strings over 255 characters.""" 473 try: 474 prop = getattr(unitClass, key) 475 size = prop.hints[u'Size'] 476 return u"VARCHAR(%s)" % size 477 except KeyError: 478 return u"VARCHAR(255)" 473 prop = getattr(unitClass, key) 474 size = prop.hints.get(u'bytes', '255') 475 return u"VARCHAR(%s)" % size 479 476 480 477 createCoercions = {datetime.datetime: lambda x, y: u"TIMESTAMP", trunk/storage/storepypgsql.py
r42 r43 18 18 import dejavu 19 19 from dejavu import storage, codewalk, logic 20 from dejavu.storage import db 20 21 import recur 21 22 22 23 23 class AdapterFromPgSQL(storage.Adapter): 24 """Coerce incoming values from libpq to Dejavu datatypes.""" 25 26 def __init__(self, unit=None): 27 self.unit = unit 28 29 def consume(self, key, value): 30 expectedType = self.unit.__class__.property_type(key) 31 value = self.coerce(value, expectedType) 32 # Set the attribute directly to avoid __set__ overhead. 33 self.unit._properties[key] = value 34 35 def pickle(self, value): 36 aType, value = value 37 if value is None: 38 return None 39 40 # Coerce to str for pickle.loads restriction. 41 value = str(value) 42 return pickle.loads(value) 43 44 def coerce_bool(self, value): 45 aType, value = value 46 if value is None: 47 return None 48 return bool(value) 49 50 def coerce_datetime_datetime(self, value): 51 aType, value = value 52 if value is None: 53 return None 54 55 chunks = (value[0:4], value[5:7], value[8:10], 56 value[11:13], value[14:16], value[17:19]) 57 return datetime.datetime(*map(int, chunks)) 58 59 def coerce_datetime_date(self, value): 60 aType, value = value 61 if value is None: 62 return None 63 64 chunks = (value[0:4], value[5:7], value[8:10]) 65 return datetime.date(*map(int, chunks)) 66 67 def coerce_datetime_time(self, value): 68 aType, value = value 69 if value is None: 70 return None 71 72 chunks = (value[9:11], value[12:14], value[15:17]) 73 return datetime.time(*map(int, chunks)) 74 75 def coerce_datetime_timedelta(self, value): 76 aType, value = value 77 if value is None: 78 return None 79 80 days, seconds = divmod(value, 1) 81 return datetime.timedelta(days, int(seconds * 86400)) 82 83 coerce_dict = pickle 84 85 def coerce_fixedpoint_FixedPoint(self, value): 86 aType, value = value 87 if value is None: 88 return None 89 return fixedpoint.FixedPoint(value) 90 91 def coerce_float(self, value): 92 aType, value = value 93 if value is None: 94 return None 95 return float(value) 96 97 def coerce_int(self, value): 98 aType, value = value 99 if value is None: 100 return None 101 return int(value) 102 103 coerce_list = pickle 104 105 def coerce_long(self, value): 106 aType, value = value 107 if value is None: 108 return None 109 return long(value) 110 111 def coerce_str(self, value): 112 aType, value = value 113 if value is None: 114 return None 115 return str(value) 116 117 coerce_tuple = pickle 118 119 def coerce_unicode(self, value): 120 aType, value = value 121 if value is None: 122 return None 123 124 # We should really inspect the pg_database system catalog 125 # to determine the encoding. Meh. 126 return unicode(value, "ASCII") 127 128 129 class AdapterToPgSQL(storage.Adapter): 130 """Coerce Expression constants to Pg SQL.""" 131 132 def tostr(self, value): 133 return str(value) 134 135 def coerce_NoneType(self, value): 136 return "NULL" 137 138 def coerce_bool(self, value): 139 if value: 140 return 'TRUE' 141 return 'FALSE' 142 143 def coerce_datetime_datetime(self, value): 144 return (u"'%04d-%02d-%02d %02d:%02d:%02d'" % 145 (value.year, value.month, value.day, 146 value.hour, value.minute, value.second)) 147 148 def coerce_datetime_date(self, value): 149 return u"'%04d-%02d-%02d'" % (value.year, value.month, value.day) 150 151 def coerce_datetime_time(self, value): 152 return u"'%02d:%02d:%02d'" % (value.hour, value.minute, value.second) 153 154 def coerce_datetime_timedelta(self, value): 155 float_val = value.days + (value.seconds / 86400.0) 156 return repr(float_val) 157 158 coerce_fixedpoint_FixedPoint = tostr 159 coerce_float = tostr 160 coerce_int = tostr 161 162 def coerce_list(self, value): 163 return "(" + ", ".join([self.coerce(x) for x in value]) + ")" 164 165 coerce_long = tostr 166 167 def coerce_str(self, value): 168 value = value.replace("'", "''") 169 value = value.replace("\\", r"\\") 170 value = value.replace("%", r"\%") 171 value = value.replace("_", r"\_") 172 return "'" + value + "'" 173 174 coerce_tuple = coerce_list 175 176 coerce_unicode = coerce_str 24 AdapterToPgSQL = db.AdapterToSQL() 25 AdapterFromPg = db.AdapterFromDB 177 26 178 27 179 28 def containedby(op1, op2, notin=False): 180 # This test doesn't work right, now that we use lists as 181 # well as tuples with IN. Need a way to mark field refs. 182 if op2.startswith('"') and op2.endswith('"'): 29 if isinstance(op1, db.ConstWrapper): 183 30 # Looking for text in a field. Use Like (reverse terms). 184 31 value = op2 + " LIKE '%" + op1[1:-1] + "%'" 185 32 else: 186 33 # Looking for field in (a, b, c) 187 value = op1 + " IN " + op2 34 atoms = [AdapterToPgSQL.coerce(x) for x in op2.basevalue] 35 value = op1 + " IN (" + ", ".join(atoms) + ")" 188 36 if notin: 189 37 value = "NOT " + value … … 191 39 192 40 def icontainedby(op1, op2, notin=False): 193 # This test doesn't work right, now that we use lists as 194 # well as tuples with IN. Need a way to mark field refs. 195 if op2.startswith('"') and op2.endswith('"'): 41 if isinstance(op1, db.ConstWrapper): 196 42 # Looking for text in a field. Use Like (reverse terms). 197 43 value = op2 + " ILIKE '%" + op1[1:-1] + "%'" 198 44 else: 199 45 # Looking for field in (a, b, c) 200 value = "lower(" + op1 + ") IN " + op2.lower() 46 atoms = [AdapterToPgSQL.coerce(x).lower() for x in op2.basevalue] 47 value = "LOWER(" + op1 + ") IN (" + ", ".join(atoms) + ")" 201 48 if notin: 202 49 value = "NOT " + value … … 210 57 211 58 class PgSQLDecompiler(codewalk.LambdaDecompiler): 212 """PgSQLDecompiler(store, unitClass, expr, adapter=AdapterToPgSQL ()).59 """PgSQLDecompiler(store, unitClass, expr, adapter=AdapterToPgSQL). 213 60 214 61 Produce SQL from a supplied Expression object, with a lambda of the form: … … 231 78 dejavu.today: lambda: "CURRENT_DATE", 232 79 dejavu.year: lambda x: "date_part('year', " + x + ")", 80 len: lambda x: "length(" + x + ")", 233 81 } 234 82 235 def __init__(self, store, unitClass, expr, adapter=AdapterToPgSQL ()):83 def __init__(self, store, unitClass, expr, adapter=AdapterToPgSQL): 236 84 self.tablename = store.prefix + unitClass.__name__ 237 85 self.expr = expr … … 254 102 term, operation = terms.pop() 255 103 # All this checking of cannot_represent is done so that a 256 # function (like dejavu.iscurrentweek) can be labeled257 # imperfect--all Units (which match the rest of the258 # Expression) will be recalled. They can then be259 # compared in expr.evaluate(unit).104 # function can be labeled imperfect. For example, the function 105 # dejavu.iscurrentweek has no PG SQL equivalent. All Units 106 # (which match the rest of the Expression) will be recalled. 107 # They can then be compared in expr.evaluate(unit). 260 108 if term is not cannot_represent: 261 109 if comp is cannot_represent: … … 289 137 # Some constants are function or class objects, 290 138 # which should not be coerced. 291 no_coerce = (FunctionType, type) 292 if isinstance(val, no_coerce): 293 pass 294 elif isinstance(val, type(len)): 295 val = str(val) 296 else: 297 val = self.adapter.coerce(val) 139 no_coerce = (FunctionType, type, 140 type(len), # <type 'builtin_function_or_method'> 141 ) 142 if not isinstance(val, no_coerce): 143 val = db.ConstWrapper(val, self.adapter.coerce(val)) 298 144 self.stack.append(val) 299 145 … … 335 181 self.stack.append(tos + " LIKE '%" + args[0][1:-1] + "'") 336 182 return 337 elif isinstance(func, basestring) and func == '<built-in function len>':338 self.stack.append("length(" + args[0] + ")")339 183 return 340 184 … … 347 191 def visit_COMPARE_OP(self, lo, hi): 348 192 op2, op1 = self.stack.pop(), self.stack.pop() 349 op = self.sql_cmp_op[lo + (hi << 8)]350 if op == 'in':351 self.stack.append(containedby(op1, op2 ))193 op = lo + (hi << 8) 194 if op in (6, 7): # in, not in 195 self.stack.append(containedby(op1, op2, op == 7)) 352 196 self.imperfect = True 353 elif op == 'not in': 354 self.stack.append(containedby(op1, op2, True)) 355 self.imperfect = True 356 elif op == '=' and op2 == 'NULL': 357 self.stack.append(op1 + " IS NULL") 358 elif op == '=' and op1 == 'NULL': 359 self.stack.append(op2 + " IS NULL") 360 elif op == '!=' and op2 == 'NULL': 361 self.stack.append(op1 + " IS NOT NULL") 362 elif op == '!=' and op1 == 'NULL': 363 self.stack.append(op2 + " IS NOT NULL") 364 else: 365 self.stack.append(op1 + " " + op + " " + op2) 197 elif op1 == 'NULL': 198 if op == 2: 199 self.stack.append(op2 + " IS NULL") 200 elif op == 3: 201 self.stack.append(op2 + " IS NOT NULL") 202 else: 203 raise ValueError("Non-equality Null comparisons not allowed.") 204 elif op2 == 'NULL': 205 if op == 2: 206 self.stack.append(op1 + " IS NULL") 207 elif op == 3: 208 self.stack.append(op1 + " IS NOT NULL") 209 else: 210 raise ValueError("Non-equality Null comparisons not allowed.") 211 else: 212 # Comparison operators for strings are case-sensitive in PG. 213 self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 366 214 367 215 def binary_op(self, op): … … 390 238 391 239 392 def safe_name(content):393 if len(content) > 63:394 warnings.warn("Identifier is longer than 63 characters. Most "395 "installations of Postgres are limited to 63.\n"396 "See NAMEDATALEN.")397 return content398 399 400 240 class StoreIteratorPgSQL(object): 401 241 """Iterator for populating Units from storage.""" … … 418 258 for row in xrange(res.ntuples): 419 259 unit = self.unitClass() 420 coercer = AdapterFromPg SQL(unit)260 coercer = AdapterFromPg(unit) 421 261 for key in unit.__class__.properties(): 422 262 index, ftype = columns[key] 423 263 value = res.getvalue(row, index) 424 264 try: 425 coercer.consume(key, (ftype, value))265 coercer.consume(key, value, ftype) 426 266 except Exception, x: 427 267 x.args += (key, ftype, value) … … 434 274 435 275 436 class FieldTypeAdapter(object):437 """Return the SQL typename of a DB column."""438 439 def coerce(self, cls, key):440 """coerce(cls, key) -> SQL typename for valuetype."""441 valuetype = cls.property_type(key)442 mod = valuetype.__module__443 if mod == "__builtin__":444 xform = "coerce_%s" % valuetype.__name__445 else:446 xform = "coerce_%s_%s" % (mod, valuetype.__name__)447 xform = xform.replace(".", "_")448 try:449 xform = getattr(self, xform)450 except AttributeError:451 raise TypeError("'%s' is not handled by %s." %452 (valuetype, self.__class__))453 return xform(cls, key)454 455 def _create_str_storage(self, cls, key):456 """This basic string handler does not know anything about the size457 limitations of the particular database. You should use one of the458 subclasses for your particular database if you need storage for459 strings over 255 characters.460 """461 prop = getattr(cls, key)462 size = int(prop.hints.get(u'Size', '0'))463 if size:464 return u"varchar(%s)" % size465 else:466 return u"text"467 468 def coerce_bool(self, cls, key): return u"boolean"469 470 def coerce_datetime_datetime(self, cls, key): return u"timestamp"471 def coerce_datetime_date(self, cls, key): return u"date"472 def coerce_datetime_time(self, cls, key): return u"time"473 474 def coerce_datetime_timedelta(self, cls, key):475 # I was seriously disinterested in writing a parser for interval.476 return u"float8"477 478 def coerce_decimal(self, cls, key): return u"numeric"479 480 coerce_dict = _create_str_storage481 482 def coerce_fixedpoint_FixedPoint(self, cls, key): return u"float8"483 def coerce_float(self, cls, key): return u"float8"484 def coerce_int(self, cls, key): return u"bigint"485 486 def coerce_long(self, cls, key): return u"decimal"487 488 coerce_list = _create_str_storage489 coerce_str = _create_str_storage490 coerce_tuple = _create_str_storage491 coerce_unicode = _create_str_storage492 493 494 276 class StorageManagerPgSQL(storage.StorageManager): 495 277 """StoreManager to save and retrieve Units via pyPgSQL 1.35.""" 496 278 497 279 decompiler = PgSQLDecompiler 498 createAdapter = FieldTypeAdapter()280 createAdapter = db.FieldTypeAdapter() 499 281 threaded = False 500 282 … … 524 306 self.prefix = allOptions.get(u'Prefix', u"djv") 525 307 self.reserve_lock = threading.Lock() 308 309 def identifier(self, *atoms): 310 ident = '"' + ''.join(map(str, atoms)).replace('"', '""') + '"' 311 if len(ident) > 63: 312 warnings.warn("Identifier is longer than 63 characters. Most " 313 "installations of Postgres are limited to 63. " 314 "See NAMEDATALEN.") 315 return ident 526 316 527 317 def shutdown(self): … … 553 343 tmplconn += "%s=%s " % (k, v) 554 344 conn = libpq.PQconnectdb(tmplconn) 555 self.execute('CREATE DATABASE "%s"' % self.dbname, conn)345 self.execute('CREATE DATABASE %s' % self.identifier(self.dbname), conn) 556 346 557 347 def select(self, unitClass, expr, distinct_fields=None): 558 tablename = self. prefix + safe_name(unitClass.__name__)348 tablename = self.identifier(self.prefix, unitClass.__name__) 559 349 if distinct_fields: 560 distinct_fields = [ u'"%s"' % xfor x in distinct_fields]561 sql = (u'SELECT DISTINCT %s FROM "%s"' %350 distinct_fields = [self.identifier(x) for x in distinct_fields] 351 sql = (u'SELECT DISTINCT %s FROM %s' % 562 352 (u', '.join(distinct_fields), tablename)) 563 353 else: 564 sql = u'SELECT * FROM "%s"' % tablename354 sql = u'SELECT * FROM %s' % tablename 565 355 w, i = self.where(unitClass, expr) 566 356 if len(w) > 0: … … 595 385 The ID should be supplied by UnitSequencers via reserve(). 596 386 """ 387 clsname = unit.__class__.__name__ 388 tblname = self.identifier(self.prefix, clsname) 389 id = self.identifier("ID") 597 390 self.reserve_lock.acquire() 598 391 try: 599 392 if unit.ID is None: 600 393 data = [] 601 clsname = unit.__class__.__name__ 602 tblname = '"%s%s"' % (self.prefix, safe_name(clsname)) 603 res = self.execute(u'SELECT "ID" FROM %s;' % tblname) 394 res = self.execute(u'SELECT %s FROM %s;' % (id, tblname)) 604 395 if res.resultType != libpq.EMPTY_QUERY: 605 396 data = [res.getvalue(row, 0) for row in xrange(res.ntuples)] 606 397 unit.ID = unit.sequencer.next(data) 607 608 self.execute('INSERT INTO %s ("ID") VALUES (%s)' %609 (tblname, AdapterToPgSQL().coerce(unit.ID)))398 399 self.execute('INSERT INTO %s (%s) VALUES (%s)' % 400 (tblname, id, AdapterToPgSQL.coerce(unit.ID))) 610 401 finally: 611 402 self.reserve_lock.release() … … 616 407 cls = unit.__class__ 617 408 clsname = cls.__name__ 618 tablename = '"%s%s"' % (self.prefix, safe_name(clsname))409 tablename = self.identifier(self.prefix, clsname) 619 410 620 fmt = AdapterToPgSQL()621 411 parms = [] 622 412 for key in cls.properties(): 623 413 if key != "ID": 624 val = fmt.coerce(getattr(unit, key))625 parms.append(' "%s" = %s' % (key, val))626 sql = ('UPDATE %s SET %s WHERE "ID"= %s' %414 val = AdapterToPgSQL.coerce(getattr(unit, key)) 415 parms.append('%s = %s' % (self.identifier(key), val)) 416 sql = ('UPDATE %s SET %s WHERE %s = %s' % 627 417 (tablename, u", ".join(parms), 628 fmt.coerce(unit.ID, cls.property_type("ID")))) 418 self.identifier("ID"), 419 AdapterToPgSQL.coerce(unit.ID, cls.property_type("ID")))) 629 420 self.execute(sql) 630 421 unit.cleanse() … … 633 424 """Delete the unit.""" 634 425 # Use a DELETE command instead of a cursor for better performance. 635 deleteStatement = (u'DELETE * FROM "%s%s" WHERE "ID" = %s' % 636 (self.prefix, safe_name(unit.__class__.__name__), 637 AdapterToPgSQL().coerce(unit.ID))) 426 deleteStatement = (u'DELETE * FROM %s WHERE %s = %s' % 427 (self.identifier(self.prefix, unit.__class__.__name__), 428 self.identifier("ID"), 429 AdapterToPgSQL.coerce(unit.ID))) 638 430 self.execute(deleteStatement) 639 431 640 432 def create_storage(self, unitClass): 641 clsname = safe_name(unitClass.__name__) 642 tblname = '"%s%s"' % (self.prefix, clsname) 433 tblname = self.identifier(self.prefix, unitClass.__name__) 643 434 644 435 coerce = self.createAdapter.coerce 645 436 fields = [] 646 437 for key in unitClass.properties(): 647 fields.append(u'"%s" %s' % (key, coerce(unitClass, key))) 438 fields.append(u'%s %s' % (self.identifier(key), 439 coerce(unitClass, key))) 648 440 try: 649 441 self.execute(u'CREATE TABLE %s (%s)' % (tblname, ", ".join(fields))) … … 653 445 else: 654 446 for index in unitClass.indices(): 655 indexname = safe_name("%si%s%s" % (self.prefix, clsname, index)) 656 self.execute(u'CREATE INDEX "%s" ON %s ("%s")' 657 % (indexname, tblname, safe_name(index))) 447 indexname = self.identifier(self.prefix, "i", 448 unitClass.__name__, index) 449 self.execute(u'CREATE INDEX %s ON %s (%s)' 450 % (indexname, tblname, self.identifier(index))) 658 451 659 452 def distinct(self, cls, fields, expr=None): … … 676 469 return [] 677 470 678 coerce = AdapterFromPg SQL().coerce471 coerce = AdapterFromPg().coerce 679 472 data = [] 680 473 for row in xrange(res.ntuples): … … 683 476 expectedType = cls.property_type(field[i]) 684 477 actualType = res.ftype(i) 685 val = coerce( (actualType, res.getvalue(row, i)),expectedType)478 val = coerce(res.getvalue(row, i), actualType, expectedType) 686 479 coerced_row.append(val) 687 480 data.append(coerced_row) trunk/storage/test_storeado.py
r42 r43 35 35 36 36 # Test select all 37 sel(lambda x: True, u"SELECT * FROM [djvAnimal] WHERE T rue", False)37 sel(lambda x: True, u"SELECT * FROM [djvAnimal] WHERE TRUE", False) 38 38 39 39 # Test now(), today(), year() … … 123 123 (datetime.date(2001, 11, 15), '#11/15/2001#'), 124 124 (datetime.time(6, 30), '#06:30:00#'), 125 (True, 'T rue'),126 (None, 'N ull'),125 (True, 'TRUE'), 126 (None, 'NULL'), 127 127 ] 128 128 for initial, final in pairs: … … 136 136 trial(lambda x: x.Date == 3, "[djvAnimal].[Date] = 3", False) 137 137 trial(lambda x, **kw: (x.a == 3) and ((x.b > 1) or (x.b < -10)), 138 u'([djvAnimal].[a] = 3) and (([djvAnimal].[b] > 1) or ([djvAnimal].[b] < -10))', False) 138 u'([djvAnimal].[a] = 3) and (([djvAnimal].[b] > 1) or ' 139 u'([djvAnimal].[b] < -10))', False) 139 140 trial(lambda x, **kw: (x.Group == 3) and not (x.Name.startswith("_")) 140 141 and not (x.Name.endswith('test')), 141 u"([djvAnimal].[Group] = 3) and ((not ([djvAnimal].[Name] Like '[_]%')) " 142 u"([djvAnimal].[Group] = 3) " 143 u"and ((not ([djvAnimal].[Name] Like '[_]%')) " 142 144 u"and (not ([djvAnimal].[Name] Like '%test')))", True) 143 145 trial(lambda x: (x.Group == '3') and (x.Date > datetime.date(2004, 2, 14)), … … 146 148 # None values 147 149 trial(lambda x: x.Date != None and None != x.Date, 148 u"([djvAnimal].[Date] Is Not Null) and ([djvAnimal].[Date] Is Not Null)", False) 150 u"([djvAnimal].[Date] IS NOT NULL) and ([djvAnimal].[Date] " 151 u"IS NOT NULL)", False) 149 152 150 153 # In operator 151 154 trial(lambda x: 'tool' in x.Name or 'tool' in x.Content, 152 u"([djvAnimal].[Name] Like '%tool%') or ([djvAnimal].[Content] Like '%tool%')", True) 155 u"([djvAnimal].[Name] Like '%tool%') " 156 u"or ([djvAnimal].[Content] Like '%tool%')", True) 153 157 trial(lambda x: x.Name in ('Johann', 'Gambolputty', 'de von Ausfern'), 154 158 u"[djvAnimal].[Name] in ('Johann', 'Gambolputty', 'de von Ausfern')", True) … … 167 171 trial(lambda x: len(x.ZipStart) == len(reqZip), u"Len([djvAnimal].[ZipStart]) = 5", False) 168 172 169 # This broke on 5/10/04, because "== None" wasn't succeeding as "= N ull".170 trial(lambda x: x.DateTo == None, u"[djvAnimal].[DateTo] I s Null", False)173 # This broke on 5/10/04, because "== None" wasn't succeeding as "= NULL". 174 trial(lambda x: x.DateTo == None, u"[djvAnimal].[DateTo] IS NULL", False) 171 175 172 176 # Another one that broke sometime in 2004. Rev 32 seems to have fixed it. … … 176 180 trial(lambda x, y, z: x.Date == 3 and y.Qty > 4 and z.Qty < 20, 177 181 "([djvAnimal].[Date] = 3) and (([djvAnimal].[Qty] > 4) and ([djvAnimal].[Qty] < 20))", False) 182 183 # Pickled types 184 e = logic.Expression(lambda x: x.Animals == [1, 2, '3']) 185 decom = storeado.ADOSQLDecompiler(testSM, zoo.Exhibit, e) 186 self.assertEqual(decom.code(), 187 ("[djvExhibit].[Animals] = '(lp1\nI1\naI2\naS''3''\na.'", False)) 178 188 179 189 trunk/storage/test_storepypgsql.py
r42 r43 38 38 # dirtied via __init__ is still saved. 39 39 o = zoo.Exhibit(ID=1, Animals=[1, 2, 3]) 40 self.assertEqual(o.dirty(), True) 40 41 box = zoo.arena.new_sandbox() 41 42 box.memorize(o) 43 self.assertEqual(o.ID, 1) 42 44 box.flush_all() 43 45 … … 83 85 84 86 def test_AdapterToPgSQL(self): 85 adapter = storepypgsql.AdapterToPgSQL ()87 adapter = storepypgsql.AdapterToPgSQL 86 88 pairs = [(3, '3'), 87 89 (3.1, '3.1'), … … 132 134 trial(lambda x: dejavu.icontains(x.Name, 'tool'), """"djvAnimal"."Name" ILIKE '%tool%'""", False) 133 135 trial(lambda x: dejavu.icontainedby(x.Name, ('Johann', 'Gambolputty', 'de von Ausfern')), 134 """ lower("djvAnimal"."Name") IN ('johann', 'gambolputty', 'de von ausfern')""", False)136 """LOWER("djvAnimal"."Name") IN ('johann', 'gambolputty', 'de von ausfern')""", False) 135 137 reqZip = '92104' 136 138 trial(lambda x: len(x.ZipStart) == len(reqZip), """length("djvAnimal"."ZipStart") = 5""", False)
