0001"""
0002sqlobject.sqlbuilder
0003--------------------
0004
0005:author: Ian Bicking <ianb@colorstudy.com>
0006
0007Builds SQL expressions from normal Python expressions.
0008
0009Disclaimer
0010----------
0011
0012This program is free software; you can redistribute it and/or modify
0013it under the terms of the GNU Lesser General Public License as
0014published by the Free Software Foundation; either version 2.1 of the
0015License, or (at your option any later version.
0016
0017This program is distributed in the hope that it will be useful,
0018but WITHOUT ANY WARRANTY; without even the implied warranty of
0019MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0020GNU General Public License for more details.
0021
0022You should have received a copy of the GNU Lesser General Public
0023License along with this program; if not, write to the Free Software
0024Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
0025USA.
0026
0027Instructions
0028------------
0029
0030To begin a SQL expression, you must use some sort of SQL object -- a
0031field, table, or SQL statement (``SELECT``, ``INSERT``, etc.) You can
0032then use normal operators, with the exception of: `and`, `or`, `not`,
0033and `in`. You can use the `AND`, `OR`, `NOT`, and `IN` functions
0034instead, or you can also use `&`, `|`, and `~` for `and`, `or`, and
0035`not` respectively (however -- the precidence for these operators
0036doesn't work as you would want, so you must use many parenthesis).
0037
0038To create a sql field, table, or constant/function, use the namespaces
0039`table`, `const`, and `func`. For instance, ``table.address`` refers
0040to the ``address`` table, and ``table.address.state`` refers to the
0041``state`` field in the address table. ``const.NULL`` is the ``NULL``
0042SQL constant, and ``func.NOW()`` is the ``NOW()`` function call
0043(`const` and `func` are actually identicle, but the two names are
0044provided for clarity). Once you create this object, expressions
0045formed with it will produce SQL statements.
0046
0047The ``sqlrepr(obj)`` function gets the SQL representation of these
0048objects, as well as the proper SQL representation of basic Python
0049types (None==NULL).
0050
0051There are a number of DB-specific SQL features that this does not
0052implement. There are a bunch of normal ANSI features also not present.
0053
0054See the bottom of this module for some examples, and run it (i.e.
0055``python sql.py``) to see the results of those examples.
0056
0057"""
0058
0059
0060
0061
0062
0063import fnmatch
0064import operator
0065import re
0066import threading
0067import types
0068import weakref
0069
0070import classregistry
0071from converters import registerConverter, sqlrepr, quote_str, unquote_str
0072
0073
0074class VersionError(Exception):
0075 pass
0076class NoDefault:
0077 pass
0078
0079
0080class SQLObjectState(object):
0081 def __init__(self, soObject, connection=None):
0082 self.soObject = weakref.proxy(soObject)
0083 self.connection = connection
0084
0085
0086safeSQLRE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_\.]*$')
0087def sqlIdentifier(obj):
0088
0089 return isinstance(obj, basestring) and bool(safeSQLRE.search(obj.strip()))
0090
0091
0092def execute(expr, executor):
0093 if hasattr(expr, 'execute'):
0094 return expr.execute(executor)
0095 else:
0096 return expr
0097
0098
0099def _str_or_sqlrepr(expr, db):
0100 if isinstance(expr, basestring):
0101 return expr
0102 return sqlrepr(expr, db)
0103
0104
0105
0106
0107
0108class SQLExpression:
0109 def __add__(self, other):
0110 return SQLOp("+", self, other)
0111 def __radd__(self, other):
0112 return SQLOp("+", other, self)
0113 def __sub__(self, other):
0114 return SQLOp("-", self, other)
0115 def __rsub__(self, other):
0116 return SQLOp("-", other, self)
0117 def __mul__(self, other):
0118 return SQLOp("*", self, other)
0119 def __rmul__(self, other):
0120 return SQLOp("*", other, self)
0121 def __div__(self, other):
0122 return SQLOp("/", self, other)
0123 def __rdiv__(self, other):
0124 return SQLOp("/", other, self)
0125 def __pos__(self):
0126 return SQLPrefix("+", self)
0127 def __neg__(self):
0128 return SQLPrefix("-", self)
0129 def __pow__(self, other):
0130 return SQLConstant("POW")(self, other)
0131 def __rpow__(self, other):
0132 return SQLConstant("POW")(other, self)
0133 def __abs__(self):
0134 return SQLConstant("ABS")(self)
0135 def __mod__(self, other):
0136 return SQLModulo(self, other)
0137 def __rmod__(self, other):
0138 return SQLConstant("MOD")(other, self)
0139
0140 def __lt__(self, other):
0141 return SQLOp("<", self, other)
0142 def __le__(self, other):
0143 return SQLOp("<=", self, other)
0144 def __gt__(self, other):
0145 return SQLOp(">", self, other)
0146 def __ge__(self, other):
0147 return SQLOp(">=", self, other)
0148 def __eq__(self, other):
0149 if other is None:
0150 return ISNULL(self)
0151 else:
0152 return SQLOp("=", self, other)
0153 def __ne__(self, other):
0154 if other is None:
0155 return ISNOTNULL(self)
0156 else:
0157 return SQLOp("<>", self, other)
0158
0159 def __and__(self, other):
0160 return SQLOp("AND", self, other)
0161 def __rand__(self, other):
0162 return SQLOp("AND", other, self)
0163 def __or__(self, other):
0164 return SQLOp("OR", self, other)
0165 def __ror__(self, other):
0166 return SQLOp("OR", other, self)
0167 def __invert__(self):
0168 return SQLPrefix("NOT", self)
0169
0170 def __call__(self, *args):
0171 return SQLCall(self, args)
0172
0173 def __repr__(self):
0174 try:
0175 return self.__sqlrepr__(None)
0176 except AssertionError:
0177 return '<%s %s>' % (
0178 self.__class__.__name__, hex(id(self))[2:])
0179
0180 def __str__(self):
0181 return repr(self)
0182
0183 def __cmp__(self, other):
0184 raise VersionError, "Python 2.1+ required"
0185 def __rcmp__(self, other):
0186 raise VersionError, "Python 2.1+ required"
0187
0188 def startswith(self, s):
0189 return STARTSWITH(self, s)
0190 def endswith(self, s):
0191 return ENDSWITH(self, s)
0192 def contains(self, s):
0193 return CONTAINSSTRING(self, s)
0194
0195 def components(self):
0196 return []
0197
0198 def tablesUsed(self, db):
0199 return self.tablesUsedSet(db)
0200 def tablesUsedSet(self, db):
0201 tables = set()
0202 for table in self.tablesUsedImmediate():
0203 if hasattr(table, '__sqlrepr__'):
0204 table = sqlrepr(table, db)
0205 tables.add(table)
0206 for component in self.components():
0207 tables.update(tablesUsedSet(component, db))
0208 return tables
0209 def tablesUsedImmediate(self):
0210 return []
0211
0212
0213
0214
0215
0216def SQLExprConverter(value, db):
0217 return value.__sqlrepr__()
0218
0219registerConverter(SQLExpression, SQLExprConverter)
0220
0221def tablesUsedSet(obj, db):
0222 if hasattr(obj, "tablesUsedSet"):
0223 return obj.tablesUsedSet(db)
0224 else:
0225 return {}
0226
0227operatorMap = {
0228 "+": operator.add,
0229 "/": operator.div,
0230 "-": operator.sub,
0231 "*": operator.mul,
0232 "<": operator.lt,
0233 "<=": operator.le,
0234 "=": operator.eq,
0235 "!=": operator.ne,
0236 ">=": operator.ge,
0237 ">": operator.gt,
0238 "IN": operator.contains,
0239 "IS": operator.eq,
0240 }
0241
0242class SQLOp(SQLExpression):
0243 def __init__(self, op, expr1, expr2):
0244 self.op = op.upper()
0245 self.expr1 = expr1
0246 self.expr2 = expr2
0247 def __sqlrepr__(self, db):
0248 s1 = sqlrepr(self.expr1, db)
0249 s2 = sqlrepr(self.expr2, db)
0250 if s1[0] != '(' and s1 != 'NULL':
0251 s1 = '(' + s1 + ')'
0252 if s2[0] != '(' and s2 != 'NULL':
0253 s2 = '(' + s2 + ')'
0254 return "(%s %s %s)" % (s1, self.op, s2)
0255 def components(self):
0256 return [self.expr1, self.expr2]
0257 def execute(self, executor):
0258 if self.op == "AND":
0259 return execute(self.expr1, executor) and execute(self.expr2, executor)
0261 elif self.op == "OR":
0262 return execute(self.expr1, executor) or execute(self.expr2, executor)
0264 else:
0265 return operatorMap[self.op.upper()](execute(self.expr1, executor),
0266 execute(self.expr2, executor))
0267
0268class SQLModulo(SQLOp):
0269 def __init__(self, expr1, expr2):
0270 SQLOp.__init__(self, '%', expr1, expr2)
0271 def __sqlrepr__(self, db):
0272 if db == 'sqlite':
0273 return SQLOp.__sqlrepr__(self, db)
0274 s1 = sqlrepr(self.expr1, db)
0275 s2 = sqlrepr(self.expr2, db)
0276 return "MOD(%s, %s)" % (s1, s2)
0277
0278registerConverter(SQLOp, SQLExprConverter)
0279registerConverter(SQLModulo, SQLExprConverter)
0280
0281class SQLCall(SQLExpression):
0282 def __init__(self, expr, args):
0283 self.expr = expr
0284 self.args = args
0285 def __sqlrepr__(self, db):
0286 return "%s%s" % (sqlrepr(self.expr, db), sqlrepr(self.args, db))
0287 def components(self):
0288 return [self.expr] + list(self.args)
0289 def execute(self, executor):
0290 raise ValueError, "I don't yet know how to locally execute functions"
0291
0292registerConverter(SQLCall, SQLExprConverter)
0293
0294class SQLPrefix(SQLExpression):
0295 def __init__(self, prefix, expr):
0296 self.prefix = prefix
0297 self.expr = expr
0298 def __sqlrepr__(self, db):
0299 return "%s %s" % (self.prefix, sqlrepr(self.expr, db))
0300 def components(self):
0301 return [self.expr]
0302 def execute(self, executor):
0303 expr = execute(self.expr, executor)
0304 if prefix == "+":
0305 return expr
0306 elif prefix == "-":
0307 return -expr
0308 elif prefix.upper() == "NOT":
0309 return not expr
0310
0311registerConverter(SQLPrefix, SQLExprConverter)
0312
0313class SQLConstant(SQLExpression):
0314 def __init__(self, const):
0315 self.const = const
0316 def __sqlrepr__(self, db):
0317 return self.const
0318 def execute(self, executor):
0319 raise ValueError, "I don't yet know how to execute SQL constants"
0320
0321registerConverter(SQLConstant, SQLExprConverter)
0322
0323class SQLTrueClauseClass(SQLExpression):
0324 def __sqlrepr__(self, db):
0325 return "1 = 1"
0326 def execute(self, executor):
0327 return 1
0328
0329SQLTrueClause = SQLTrueClauseClass()
0330
0331registerConverter(SQLTrueClauseClass, SQLExprConverter)
0332
0333
0334
0335
0336
0337class Field(SQLExpression):
0338 def __init__(self, tableName, fieldName):
0339 self.tableName = tableName
0340 self.fieldName = fieldName
0341 def __sqlrepr__(self, db):
0342 return self.tableName + "." + self.fieldName
0343 def tablesUsedImmediate(self):
0344 return [self.tableName]
0345 def execute(self, executor):
0346 return executor.field(self.tableName, self.fieldName)
0347
0348class SQLObjectField(Field):
0349 def __init__(self, tableName, fieldName, original, soClass, column):
0350 Field.__init__(self, tableName, fieldName)
0351 self.original = original
0352 self.soClass = soClass
0353 self.column = column
0354 def _from_python(self, value):
0355 column = self.column
0356 if not isinstance(value, SQLExpression) and column and column.from_python:
0357 value = column.from_python(value, SQLObjectState(self.soClass))
0358 return value
0359 def __eq__(self, other):
0360 if other is None:
0361 return ISNULL(self)
0362 other = self._from_python(other)
0363 return SQLOp('=', self, other)
0364 def __ne__(self, other):
0365 if other is None:
0366 return ISNOTNULL(self)
0367 other = self._from_python(other)
0368 return SQLOp('<>', self, other)
0369 def startswith(self, s):
0370 s = self._from_python(s)
0371 return STARTSWITH(self, s)
0372 def endswith(self, s):
0373 s = self._from_python(s)
0374 return ENDSWITH(self, s)
0375 def contains(self, s):
0376 s = self._from_python(s)
0377 return CONTAINSSTRING(self, s)
0378
0379registerConverter(SQLObjectField, SQLExprConverter)
0380
0381
0382class Table(SQLExpression):
0383 FieldClass = Field
0384
0385 def __init__(self, tableName):
0386 self.tableName = tableName
0387 def __getattr__(self, attr):
0388 if attr.startswith('__'):
0389 raise AttributeError
0390 return self.FieldClass(self.tableName, attr)
0391 def __sqlrepr__(self, db):
0392 return _str_or_sqlrepr(self.tableName, db)
0393 def execute(self, executor):
0394 raise ValueError, "Tables don't have values"
0395
0396class SQLObjectTable(Table):
0397 FieldClass = SQLObjectField
0398
0399 def __init__(self, soClass):
0400 self.soClass = soClass
0401 assert soClass.sqlmeta.table, (
0402 "Bad table name in class %r: %r"
0403 % (soClass, soClass.sqlmeta.table))
0404 Table.__init__(self, soClass.sqlmeta.table)
0405
0406 def __getattr__(self, attr):
0407 if attr.startswith('__'):
0408 raise AttributeError
0409 if attr == 'id':
0410 return self._getattrFromID(attr)
0411 elif attr in self.soClass.sqlmeta.columns:
0412 column = self.soClass.sqlmeta.columns[attr]
0413 return self._getattrFromColumn(column, attr)
0414 elif attr+'ID' in [k for (k, v) in self.soClass.sqlmeta.columns.items() if v.foreignKey]:
0415 attr += 'ID'
0416 column = self.soClass.sqlmeta.columns[attr]
0417 return self._getattrFromColumn(column, attr)
0418 else:
0419 raise AttributeError("%s instance has no attribute '%s'" % (self.soClass.__name__, attr))
0420
0421 def _getattrFromID(self, attr):
0422 return self.FieldClass(self.tableName, self.soClass.sqlmeta.idName, attr, self.soClass, None)
0423
0424 def _getattrFromColumn(self, column, attr):
0425 return self.FieldClass(self.tableName, column.dbName, attr, self.soClass, column)
0426
0427class SQLObjectTableWithJoins(SQLObjectTable):
0428
0429 def __getattr__(self, attr):
0430 if attr+'ID' in [k for (k, v) in self.soClass.sqlmeta.columns.items() if v.foreignKey]:
0431 column = self.soClass.sqlmeta.columns[attr+'ID']
0432 return self._getattrFromForeignKey(column, attr)
0433 elif attr in [x.joinMethodName for x in self.soClass.sqlmeta.joins]:
0434 join = [x for x in self.soClass.sqlmeta.joins if x.joinMethodName == attr][0]
0435 return self._getattrFromJoin(join, attr)
0436 else:
0437 return SQLObjectTable.__getattr__(self, attr)
0438
0439 def _getattrFromForeignKey(self, column, attr):
0440 ret = getattr(self, column.name) == getattr(self.soClass, '_SO_class_'+column.foreignKey).q.id
0442 return ret
0443
0444 def _getattrFromJoin(self, join, attr):
0445 if hasattr(join, 'otherColumn'):
0446 return AND(join.otherClass.q.id == Field(join.intermediateTable, join.otherColumn),
0447 Field(join.intermediateTable, join.joinColumn) == self.soClass.q.id)
0448 else:
0449 return getattr(join.otherClass.q, join.joinColumn)==self.soClass.q.id
0450
0451class TableSpace:
0452 TableClass = Table
0453
0454 def __getattr__(self, attr):
0455 if attr.startswith('__'):
0456 raise AttributeError
0457 return self.TableClass(attr)
0458
0459class ConstantSpace:
0460 def __getattr__(self, attr):
0461 if attr.startswith('__'):
0462 raise AttributeError
0463 return SQLConstant(attr)
0464
0465
0466
0467
0468
0469
0470class AliasField(Field):
0471 def __init__(self, tableName, fieldName, alias, aliasTable):
0472 Field.__init__(self, tableName, fieldName)
0473 self.alias = alias
0474 self.aliasTable = aliasTable
0475
0476 def __sqlrepr__(self, db):
0477 fieldName = self.fieldName
0478 if isinstance(fieldName, SQLExpression):
0479 fieldName = sqlrepr(fieldName, db)
0480 return self.alias + "." + fieldName
0481
0482 def tablesUsedImmediate(self):
0483 return [self.aliasTable]
0484
0485class AliasTable(Table):
0486 as_string = ''
0487 FieldClass = AliasField
0488
0489 _alias_lock = threading.Lock()
0490 _alias_counter = 0
0491
0492 def __init__(self, table, alias=None):
0493 if hasattr(table, "sqlmeta"):
0494 tableName = SQLConstant(table.sqlmeta.table)
0495 elif isinstance(table, (Select, Union)):
0496 assert alias is not None, "Alias name cannot be constructed from Select instances, please provide 'alias' kw."
0497 tableName = Subquery('', table)
0498 table = None
0499 else:
0500 tableName = SQLConstant(table)
0501 table = None
0502 Table.__init__(self, tableName)
0503 self.table = table
0504 if alias is None:
0505 self._alias_lock.acquire()
0506 try:
0507 AliasTable._alias_counter += 1
0508 alias = "%s_alias%d" % (tableName, AliasTable._alias_counter)
0509 finally:
0510 self._alias_lock.release()
0511 self.alias = alias
0512
0513 def __getattr__(self, attr):
0514 if attr.startswith('__'):
0515 raise AttributeError
0516 if self.table:
0517 attr = getattr(self.table.q, attr).fieldName
0518 return self.FieldClass(self.tableName, attr, self.alias, self)
0519
0520 def __sqlrepr__(self, db):
0521 return "%s %s %s" % (sqlrepr(self.tableName, db), self.as_string, self.alias)
0522
0523class Alias(SQLExpression):
0524 def __init__(self, table, alias=None):
0525 self.q = AliasTable(table, alias)
0526
0527 def __sqlrepr__(self, db):
0528 return sqlrepr(self.q, db)
0529
0530 def components(self):
0531 return [self.q]
0532
0533
0534class Union(SQLExpression):
0535 def __init__(self, *tables):
0536 tabs = []
0537 for t in tables:
0538 if not isinstance(t, SQLExpression) and hasattr(t, 'sqlmeta'):
0539 t = t.sqlmeta.table
0540 if isinstance(t, Alias):
0541 t = t.q
0542 if isinstance(t, Table):
0543 t = t.tableName
0544 if not isinstance(t, SQLExpression):
0545 t = SQLConstant(t)
0546 tabs.append(t)
0547 self.tables = tabs
0548
0549 def __sqlrepr__(self, db):
0550 return " UNION ".join([str(sqlrepr(t, db)) for t in self.tables])
0551
0552
0553
0554
0555
0556class Select(SQLExpression):
0557 def __init__(self, items=NoDefault, where=NoDefault, groupBy=NoDefault,
0558 having=NoDefault, orderBy=NoDefault, limit=NoDefault,
0559 join=NoDefault, lazyColumns=False, distinct=False,
0560 start=0, end=None, reversed=False, forUpdate=False,
0561 clause=NoDefault, staticTables=NoDefault, distinctOn=NoDefault):
0562 self.ops = {}
0563 if not isinstance(items, (list, tuple, types.GeneratorType)):
0564 items = [items]
0565 if clause is NoDefault and where is not NoDefault:
0566 clause = where
0567 if staticTables is NoDefault:
0568 staticTables = []
0569 self.ops['items'] = items
0570 self.ops['clause'] = clause
0571 self.ops['groupBy'] = groupBy
0572 self.ops['having'] = having
0573 self.ops['orderBy'] = orderBy
0574 self.ops['limit'] = limit
0575 self.ops['join'] = join
0576 self.ops['lazyColumns'] = lazyColumns
0577 self.ops['distinct'] = distinct
0578 self.ops['distinctOn'] = distinctOn
0579 self.ops['start'] = start
0580 self.ops['end'] = end
0581 self.ops['reversed'] = reversed
0582 self.ops['forUpdate'] = forUpdate
0583 self.ops['staticTables'] = staticTables
0584
0585 def clone(self, **newOps):
0586 ops = self.ops.copy()
0587 ops.update(newOps)
0588 return self.__class__(**ops)
0589
0590 def newItems(self, items):
0591 return self.clone(items=items)
0592
0593 def newClause(self, new_clause):
0594 return self.clone(clause=new_clause)
0595
0596 def orderBy(self, orderBy):
0597 return self.clone(orderBy=orderBy)
0598
0599 def unlimited(self):
0600 return self.clone(limit=NoDefault, start=0, end=None)
0601
0602 def limit(self, limit):
0603 self.clone(limit=limit)
0604
0605 def lazyColumns(self, value):
0606 return self.clone(lazyColumns=value)
0607
0608 def reversed(self):
0609 return self.clone(reversed=not self.ops.get('reversed', False))
0610
0611 def distinct(self):
0612 return self.clone(distinct=True)
0613
0614 def filter(self, filter_clause):
0615 if filter_clause is None:
0616
0617 return self
0618 clause = self.ops['clause']
0619 if isinstance(clause, basestring):
0620 clause = SQLConstant('(%s)' % clause)
0621 return self.newClause(AND(clause, filter_clause))
0622
0623 def __sqlrepr__(self, db):
0624
0625 select = "SELECT"
0626 if self.ops['distinct']:
0627 select += " DISTINCT"
0628 if self.ops['distinctOn'] is not NoDefault:
0629 select += " ON(%s)" % _str_or_sqlrepr(self.ops['distinctOn'], db)
0630 if not self.ops['lazyColumns']:
0631 select += " %s" % ", ".join([str(_str_or_sqlrepr(v, db)) for v in self.ops['items']])
0632 else:
0633 select += " %s" % _str_or_sqlrepr(self.ops['items'][0], db)
0634
0635 join = []
0636 join_str = ''
0637 if self.ops['join'] is not NoDefault and self.ops['join'] is not None:
0638 _join = self.ops['join']
0639 if isinstance(_join, str):
0640 join_str = " " + _join
0641 elif isinstance(_join, SQLJoin):
0642 join.append(_join)
0643 else:
0644 join.extend(_join)
0645 tables = set()
0646 for x in self.ops['staticTables']:
0647 if isinstance(x, SQLExpression):
0648 x = sqlrepr(x, db)
0649 tables.add(x)
0650 things = list(self.ops['items']) + join
0651 if self.ops['clause'] is not NoDefault:
0652 things.append(self.ops['clause'])
0653 for thing in things:
0654 if isinstance(thing, SQLExpression):
0655 tables.update(tablesUsedSet(thing, db))
0656 for j in join:
0657 t1 = _str_or_sqlrepr(j.table1, db)
0658 if t1 in tables: tables.remove(t1)
0659 t2 = _str_or_sqlrepr(j.table2, db)
0660 if t2 in tables: tables.remove(t2)
0661 if tables:
0662 select += " FROM %s" % ", ".join(sorted(tables))
0663 elif join:
0664 select += " FROM"
0665 tablesYet = tables
0666 for j in join:
0667 if tablesYet and j.table1:
0668 sep = ", "
0669 else:
0670 sep = " "
0671 select += sep + sqlrepr(j, db)
0672 tablesYet = True
0673
0674 if join_str:
0675 select += join_str
0676
0677 if self.ops['clause'] is not NoDefault:
0678 select += " WHERE %s" % _str_or_sqlrepr(self.ops['clause'], db)
0679 if self.ops['groupBy'] is not NoDefault:
0680 groupBy = _str_or_sqlrepr(self.ops['groupBy'], db)
0681 if isinstance(self.ops['groupBy'], (list, tuple)):
0682 groupBy = groupBy[1:-1]
0683 select += " GROUP BY %s" % groupBy
0684 if self.ops['having'] is not NoDefault:
0685 select += " HAVING %s" % _str_or_sqlrepr(self.ops['having'], db)
0686 if self.ops['orderBy'] is not NoDefault and self.ops['orderBy'] is not None:
0687 orderBy = self.ops['orderBy']
0688 if self.ops['reversed']:
0689 reverser = DESC
0690 else:
0691 reverser = lambda x: x
0692 if isinstance(orderBy, (list, tuple)):
0693 select += " ORDER BY %s" % ", ".join([_str_or_sqlrepr(reverser(x), db) for x in orderBy])
0694 else:
0695 select += " ORDER BY %s" % _str_or_sqlrepr(reverser(orderBy), db)
0696 start, end = self.ops['start'], self.ops['end']
0697 if self.ops['limit'] is not NoDefault:
0698 end = start + self.ops['limit']
0699 if start or end:
0700 from dbconnection import dbConnectionForScheme
0701 select = dbConnectionForScheme(db)._queryAddLimitOffset(select, start, end)
0702 if self.ops['forUpdate']:
0703 select += " FOR UPDATE"
0704 return select
0705
0706registerConverter(Select, SQLExprConverter)
0707
0708class Insert(SQLExpression):
0709 def __init__(self, table, valueList=None, values=None, template=NoDefault):
0710 self.template = template
0711 self.table = table
0712 if valueList:
0713 if values:
0714 raise TypeError, "You may only give valueList *or* values"
0715 self.valueList = valueList
0716 else:
0717 self.valueList = [values]
0718 def __sqlrepr__(self, db):
0719 if not self.valueList:
0720 return ''
0721 insert = "INSERT INTO %s" % self.table
0722 allowNonDict = True
0723 template = self.template
0724 if (template is NoDefault) and isinstance(self.valueList[0], dict):
0725 template = list(sorted(self.valueList[0].keys()))
0726 allowNonDict = False
0727 if template is not NoDefault:
0728 insert += " (%s)" % ", ".join(template)
0729 insert += " VALUES "
0730 listToJoin = []
0731 listToJoin_app = listToJoin.append
0732 for value in self.valueList:
0733 if isinstance(value, dict):
0734 if template is NoDefault:
0735 raise TypeError, "You can't mix non-dictionaries with dictionaries in an INSERT if you don't provide a template (%s)" % repr(value)
0736 value = dictToList(template, value)
0737 elif not allowNonDict:
0738 raise TypeError, "You can't mix non-dictionaries with dictionaries in an INSERT if you don't provide a template (%s)" % repr(value)
0739 listToJoin_app("(%s)" % ", ".join([sqlrepr(v, db) for v in value]))
0740 insert = "%s%s" % (insert, ", ".join(listToJoin))
0741 return insert
0742
0743registerConverter(Insert, SQLExprConverter)
0744
0745def dictToList(template, dict):
0746 list = []
0747 for key in template:
0748 list.append(dict[key])
0749 if len(dict.keys()) > len(template):
0750 raise TypeError, "Extra entries in dictionary that aren't asked for in template (template=%s, dict=%s)" % (repr(template), repr(dict))
0751 return list
0752
0753class Update(SQLExpression):
0754 def __init__(self, table, values, template=NoDefault, where=NoDefault):
0755 self.table = table
0756 self.values = values
0757 self.template = template
0758 self.whereClause = where
0759 def __sqlrepr__(self, db):
0760 update = "%s %s" % (self.sqlName(), self.table)
0761 update += " SET"
0762 first = True
0763 if self.template is not NoDefault:
0764 for i in range(len(self.template)):
0765 if first:
0766 first = False
0767 else:
0768 update += ","
0769 update += " %s=%s" % (self.template[i], sqlrepr(self.values[i], db))
0770 else:
0771 for key, value in sorted(self.values.items()):
0772 if first:
0773 first = False
0774 else:
0775 update += ","
0776 update += " %s=%s" % (key, sqlrepr(value, db))
0777 if self.whereClause is not NoDefault:
0778 update += " WHERE %s" % _str_or_sqlrepr(self.whereClause, db)
0779 return update
0780 def sqlName(self):
0781 return "UPDATE"
0782
0783registerConverter(Update, SQLExprConverter)
0784
0785class Delete(SQLExpression):
0786 """To be safe, this will signal an error if there is no where clause,
0787 unless you pass in where=None to the constructor."""
0788 def __init__(self, table, where=NoDefault):
0789 self.table = table
0790 if where is NoDefault:
0791 raise TypeError, "You must give a where clause or pass in None to indicate no where clause"
0792 self.whereClause = where
0793 def __sqlrepr__(self, db):
0794 whereClause = self.whereClause
0795 if whereClause is None:
0796 return "DELETE FROM %s" % self.table
0797 whereClause = _str_or_sqlrepr(whereClause, db)
0798 return "DELETE FROM %s WHERE %s" % (self.table, whereClause)
0799
0800registerConverter(Delete, SQLExprConverter)
0801
0802class Replace(Update):
0803 def sqlName(self):
0804 return "REPLACE"
0805
0806registerConverter(Replace, SQLExprConverter)
0807
0808
0809
0810
0811
0812class DESC(SQLExpression):
0813
0814 def __init__(self, expr):
0815 self.expr = expr
0816
0817 def __sqlrepr__(self, db):
0818 if isinstance(self.expr, DESC):
0819 return sqlrepr(self.expr.expr, db)
0820 return '%s DESC' % sqlrepr(self.expr, db)
0821
0822def AND(*ops):
0823 if not ops:
0824 return None
0825 op1 = ops[0]
0826 ops = ops[1:]
0827 if ops:
0828 return SQLOp("AND", op1, AND(*ops))
0829 else:
0830 return op1
0831
0832def OR(*ops):
0833 if not ops:
0834 return None
0835 op1 = ops[0]
0836 ops = ops[1:]
0837 if ops:
0838 return SQLOp("OR", op1, OR(*ops))
0839 else:
0840 return op1
0841
0842def NOT(op):
0843 return SQLPrefix("NOT", op)
0844
0845def _IN(item, list):
0846 return SQLOp("IN", item, list)
0847
0848def IN(item, list):
0849 from sresults import SelectResults
0850 if isinstance(list, SelectResults):
0851 query = list.queryForSelect()
0852 query.ops['items'] = [list.sourceClass.q.id]
0853 list = query
0854 if isinstance(list, Select):
0855 return INSubquery(item, list)
0856 else:
0857 return _IN(item, list)
0858
0859def NOTIN(item, list):
0860 if isinstance(list, Select):
0861 return NOTINSubquery(item, list)
0862 else:
0863 return NOT(_IN(item, list))
0864
0865def STARTSWITH(expr, pattern):
0866 return LIKE(expr, _LikeQuoted(pattern) + '%', escape='\\')
0867
0868def ENDSWITH(expr, pattern):
0869 return LIKE(expr, '%' + _LikeQuoted(pattern), escape='\\')
0870
0871def CONTAINSSTRING(expr, pattern):
0872 return LIKE(expr, '%' + _LikeQuoted(pattern) + '%', escape='\\')
0873
0874def ISNULL(expr):
0875 return SQLOp("IS", expr, None)
0876
0877def ISNOTNULL(expr):
0878 return SQLOp("IS NOT", expr, None)
0879
0880class ColumnAS(SQLOp):
0881 ''' Just like SQLOp('AS', expr, name) except without the parentheses '''
0882 def __init__(self, expr, name):
0883 if isinstance(name, basestring):
0884 name = SQLConstant(name)
0885 SQLOp.__init__(self, 'AS', expr, name)
0886 def __sqlrepr__(self, db):
0887 return "%s %s %s" % (sqlrepr(self.expr1, db), self.op, sqlrepr(self.expr2, db))
0888
0889class _LikeQuoted:
0890
0891
0892
0893
0894
0895 def __init__(self, expr):
0896 self.expr = expr
0897 self.prefix = ''
0898 self.postfix = ''
0899
0900 def __radd__(self, s):
0901 self.prefix = s + self.prefix
0902 return self
0903
0904 def __add__(self, s):
0905 self.postfix += s
0906 return self
0907
0908 def __sqlrepr__(self, db):
0909 s = self.expr
0910 if isinstance(s, SQLExpression):
0911 values = []
0912 if self.prefix:
0913 values.append(quote_str(self.prefix, db))
0914 s = _quote_like_special(sqlrepr(s, db), db)
0915 values.append(s)
0916 if self.postfix:
0917 values.append(quote_str(self.postfix, db))
0918 if db == "mysql":
0919 return "CONCAT(%s)" % ", ".join(values)
0920 else:
0921 return " || ".join(values)
0922 elif isinstance(s, basestring):
0923 s = _quote_like_special(unquote_str(sqlrepr(s, db)), db)
0924 return quote_str("%s%s%s" % (self.prefix, s, self.postfix), db)
0925 else:
0926 raise TypeError, "expected str, unicode or SQLExpression, got %s" % type(s)
0927
0928def _quote_like_special(s, db):
0929 if db in ('postgres', 'rdbhost'):
0930 escape = r'\\'
0931 else:
0932 escape = '\\'
0933 s = s.replace('\\', r'\\').replace('%', escape+'%').replace('_', escape+'_')
0934 return s
0935
0936class CONCAT:
0937 def __init__(self, *expressions):
0938 self.expressions = expressions
0939
0940 def __sqlrepr__(self, db):
0941 values = [sqlrepr(expr, db) for expr in self.expressions]
0942 if db == "mysql":
0943 return "CONCAT(%s)" % ", ".join(values)
0944 else:
0945 return " || ".join(values)
0946
0947
0948
0949
0950
0951class SQLJoin(SQLExpression):
0952 def __init__(self, table1, table2, op=','):
0953 if hasattr(table1, 'sqlmeta'):
0954 table1 = table1.sqlmeta.table
0955 if hasattr(table2, 'sqlmeta'):
0956 table2 = table2.sqlmeta.table
0957 if isinstance(table1, str):
0958 table1 = SQLConstant(table1)
0959 if isinstance(table2, str):
0960 table2 = SQLConstant(table2)
0961 self.table1 = table1
0962 self.table2 = table2
0963 self.op = op
0964
0965 def __sqlrepr__(self, db):
0966 if self.table1:
0967 return "%s%s %s" % (sqlrepr(self.table1, db), self.op, sqlrepr(self.table2, db))
0968 else:
0969 return "%s %s" % (self.op, sqlrepr(self.table2, db))
0970
0971registerConverter(SQLJoin, SQLExprConverter)
0972
0973def JOIN(table1, table2):
0974 return SQLJoin(table1, table2, " JOIN")
0975
0976def INNERJOIN(table1, table2):
0977 return SQLJoin(table1, table2, " INNER JOIN")
0978
0979def CROSSJOIN(table1, table2):
0980 return SQLJoin(table1, table2, " CROSS JOIN")
0981
0982def STRAIGHTJOIN(table1, table2):
0983 return SQLJoin(table1, table2, " STRAIGHT JOIN")
0984
0985def LEFTJOIN(table1, table2):
0986 return SQLJoin(table1, table2, " LEFT JOIN")
0987
0988def LEFTOUTERJOIN(table1, table2):
0989 return SQLJoin(table1, table2, " LEFT OUTER JOIN")
0990
0991def NATURALJOIN(table1, table2):
0992 return SQLJoin(table1, table2, " NATURAL JOIN")
0993
0994def NATURALLEFTJOIN(table1, table2):
0995 return SQLJoin(table1, table2, " NATURAL LEFT JOIN")
0996
0997def NATURALLEFTOUTERJOIN(table1, table2):
0998 return SQLJoin(table1, table2, " NATURAL LEFT OUTER JOIN")
0999
1000def RIGHTJOIN(table1, table2):
1001 return SQLJoin(table1, table2, " RIGHT JOIN")
1002
1003def RIGHTOUTERJOIN(table1, table2):
1004 return SQLJoin(table1, table2, " RIGHT OUTER JOIN")
1005
1006def NATURALRIGHTJOIN(table1, table2):
1007 return SQLJoin(table1, table2, " NATURAL RIGHT JOIN")
1008
1009def NATURALRIGHTOUTERJOIN(table1, table2):
1010 return SQLJoin(table1, table2, " NATURAL RIGHT OUTER JOIN")
1011
1012def FULLJOIN(table1, table2):
1013 return SQLJoin(table1, table2, " FULL JOIN")
1014
1015def FULLOUTERJOIN(table1, table2):
1016 return SQLJoin(table1, table2, " FULL OUTER JOIN")
1017
1018def NATURALFULLJOIN(table1, table2):
1019 return SQLJoin(table1, table2, " NATURAL FULL JOIN")
1020
1021def NATURALFULLOUTERJOIN(table1, table2):
1022 return SQLJoin(table1, table2, " NATURAL FULL OUTER JOIN")
1023
1024class SQLJoinConditional(SQLJoin):
1025 """Conditional JOIN"""
1026 def __init__(self, table1, table2, op, on_condition=None, using_columns=None):
1027 """For condition you must give on_condition or using_columns but not both
1028
1029 on_condition can be a string or SQLExpression, for example
1030 Table1.q.col1 == Table2.q.col2
1031 using_columns can be a string or a list of columns, e.g.
1032 (Table1.q.col1, Table2.q.col2)
1033 """
1034 if not on_condition and not using_columns:
1035 raise TypeError, "You must give ON condition or USING columns"
1036 if on_condition and using_columns:
1037 raise TypeError, "You must give ON condition or USING columns but not both"
1038 SQLJoin.__init__(self, table1, table2, op)
1039 self.on_condition = on_condition
1040 self.using_columns = using_columns
1041
1042 def __sqlrepr__(self, db):
1043 if self.on_condition:
1044 on_condition = self.on_condition
1045 if hasattr(on_condition, "__sqlrepr__"):
1046 on_condition = sqlrepr(on_condition, db)
1047 join = "%s %s ON %s" % (self.op, sqlrepr(self.table2, db), on_condition)
1048 if self.table1:
1049 join = "%s %s" % (sqlrepr(self.table1, db), join)
1050 return join
1051 elif self.using_columns:
1052 using_columns = []
1053 for col in self.using_columns:
1054 if hasattr(col, "__sqlrepr__"):
1055 col = sqlrepr(col, db)
1056 using_columns.append(col)
1057 using_columns = ", ".join(using_columns)
1058 join = "%s %s USING (%s)" % (self.op, sqlrepr(self.table2, db), using_columns)
1059 if self.table1:
1060 join = "%s %s" % (sqlrepr(self.table1, db), join)
1061 return join
1062 else:
1063 RuntimeError, "Impossible error"
1064
1065registerConverter(SQLJoinConditional, SQLExprConverter)
1066
1067def INNERJOINConditional(table1, table2, on_condition=None, using_columns=None):
1068 return SQLJoinConditional(table1, table2, "INNER JOIN", on_condition, using_columns)
1069
1070def LEFTJOINConditional(table1, table2, on_condition=None, using_columns=None):
1071 return SQLJoinConditional(table1, table2, "LEFT JOIN", on_condition, using_columns)
1072
1073def LEFTOUTERJOINConditional(table1, table2, on_condition=None, using_columns=None):
1074 return SQLJoinConditional(table1, table2, "LEFT OUTER JOIN", on_condition, using_columns)
1075
1076def RIGHTJOINConditional(table1, table2, on_condition=None, using_columns=None):
1077 return SQLJoinConditional(table1, table2, "RIGHT JOIN", on_condition, using_columns)
1078
1079def RIGHTOUTERJOINConditional(table1, table2, on_condition=None, using_columns=None):
1080 return SQLJoinConditional(table1, table2, "RIGHT OUTER JOIN", on_condition, using_columns)
1081
1082def FULLJOINConditional(table1, table2, on_condition=None, using_columns=None):
1083 return SQLJoinConditional(table1, table2, "FULL JOIN", on_condition, using_columns)
1084
1085def FULLOUTERJOINConditional(table1, table2, on_condition=None, using_columns=None):
1086 return SQLJoinConditional(table1, table2, "FULL OUTER JOIN", on_condition, using_columns)
1087
1088class SQLJoinOn(SQLJoinConditional):
1089 """Conditional JOIN ON"""
1090 def __init__(self, table1, table2, op, on_condition):
1091 SQLJoinConditional.__init__(self, table1, table2, op, on_condition)
1092
1093registerConverter(SQLJoinOn, SQLExprConverter)
1094
1095class SQLJoinUsing(SQLJoinConditional):
1096 """Conditional JOIN USING"""
1097 def __init__(self, table1, table2, op, using_columns):
1098 SQLJoinConditional.__init__(self, table1, table2, op, None, using_columns)
1099
1100registerConverter(SQLJoinUsing, SQLExprConverter)
1101
1102def INNERJOINOn(table1, table2, on_condition):
1103 return SQLJoinOn(table1, table2, "INNER JOIN", on_condition)
1104
1105def LEFTJOINOn(table1, table2, on_condition):
1106 return SQLJoinOn(table1, table2, "LEFT JOIN", on_condition)
1107
1108def LEFTOUTERJOINOn(table1, table2, on_condition):
1109 return SQLJoinOn(table1, table2, "LEFT OUTER JOIN", on_condition)
1110
1111def RIGHTJOINOn(table1, table2, on_condition):
1112 return SQLJoinOn(table1, table2, "RIGHT JOIN", on_condition)
1113
1114def RIGHTOUTERJOINOn(table1, table2, on_condition):
1115 return SQLJoinOn(table1, table2, "RIGHT OUTER JOIN", on_condition)
1116
1117def FULLJOINOn(table1, table2, on_condition):
1118 return SQLJoinOn(table1, table2, "FULL JOIN", on_condition)
1119
1120def FULLOUTERJOINOn(table1, table2, on_condition):
1121 return SQLJoinOn(table1, table2, "FULL OUTER JOIN", on_condition)
1122
1123def INNERJOINUsing(table1, table2, using_columns):
1124 return SQLJoinUsing(table1, table2, "INNER JOIN", using_columns)
1125
1126def LEFTJOINUsing(table1, table2, using_columns):
1127 return SQLJoinUsing(table1, table2, "LEFT JOIN", using_columns)
1128
1129def LEFTOUTERJOINUsing(table1, table2, using_columns):
1130 return SQLJoinUsing(table1, table2, "LEFT OUTER JOIN", using_columns)
1131
1132def RIGHTJOINUsing(table1, table2, using_columns):
1133 return SQLJoinUsing(table1, table2, "RIGHT JOIN", using_columns)
1134
1135def RIGHTOUTERJOINUsing(table1, table2, using_columns):
1136 return SQLJoinUsing(table1, table2, "RIGHT OUTER JOIN", using_columns)
1137
1138def FULLJOINUsing(table1, table2, using_columns):
1139 return SQLJoinUsing(table1, table2, "FULL JOIN", using_columns)
1140
1141def FULLOUTERJOINUsing(table1, table2, using_columns):
1142 return SQLJoinUsing(table1, table2, "FULL OUTER JOIN", using_columns)
1143
1144
1145
1146
1147
1148
1149class OuterField(SQLObjectField):
1150 def tablesUsedImmediate(self):
1151 return []
1152
1153class OuterTable(SQLObjectTable):
1154 FieldClass = OuterField
1155
1156class Outer:
1157 def __init__(self, table):
1158 self.q = OuterTable(table)
1159
1160
1161class LIKE(SQLExpression):
1162 op = "LIKE"
1163
1164 def __init__(self, expr, string, escape=None):
1165 self.expr = expr
1166 self.string = string
1167 self.escape = escape
1168 def __sqlrepr__(self, db):
1169 escape = self.escape
1170 like = "%s %s (%s)" % (sqlrepr(self.expr, db), self.op, sqlrepr(self.string, db))
1171 if escape is None:
1172 return "(%s)" % like
1173 else:
1174 return "(%s ESCAPE %s)" % (like, sqlrepr(escape, db))
1175 def components(self):
1176 return [self.expr, self.string]
1177 def execute(self, executor):
1178 if not hasattr(self, '_regex'):
1179
1180 dest = self.string
1181 dest = dest.replace("%%", "\001")
1182 dest = dest.replace("*", "\002")
1183 dest = dest.replace("%", "*")
1184 dest = dest.replace("\001", "%")
1185 dest = dest.replace("\002", "[*]")
1186 self._regex = re.compile(fnmatch.translate(dest), re.I)
1187 return self._regex.search(execute(self.expr, executor))
1188
1189class RLIKE(LIKE):
1190 op = "RLIKE"
1191
1192 op_db = {
1193 'firebird': 'RLIKE',
1194 'maxdb': 'RLIKE',
1195 'mysql': 'RLIKE',
1196 'postgres': '~',
1197 'rdbhost': '~',
1198 'sqlite': 'REGEXP'
1199 }
1200
1201 def _get_op(self, db):
1202 return self.op_db.get(db, 'LIKE')
1203 def __sqlrepr__(self, db):
1204 return "(%s %s (%s))" % (
1205 sqlrepr(self.expr, db), self._get_op(db), sqlrepr(self.string, db)
1206 )
1207 def execute(self, executor):
1208 self.op = self._get_op(self.db)
1209 return LIKE.execute(self, executor)
1210
1211
1212class INSubquery(SQLExpression):
1213 op = "IN"
1214
1215 def __init__(self, item, subquery):
1216 self.item = item
1217 self.subquery = subquery
1218 def components(self):
1219 return [self.item]
1220 def __sqlrepr__(self, db):
1221 return "%s %s (%s)" % (sqlrepr(self.item, db), self.op, sqlrepr(self.subquery, db))
1222
1223class NOTINSubquery(INSubquery):
1224 op = "NOT IN"
1225
1226
1227class Subquery(SQLExpression):
1228 def __init__(self, op, subquery):
1229 self.op = op
1230 self.subquery = subquery
1231
1232 def __sqlrepr__(self, db):
1233 return "%s (%s)" % (self.op, sqlrepr(self.subquery, db))
1234
1235def EXISTS(subquery):
1236 return Subquery("EXISTS", subquery)
1237
1238def NOTEXISTS(subquery):
1239 return Subquery("NOT EXISTS", subquery)
1240
1241def SOME(subquery):
1242 return Subquery("SOME", subquery)
1243
1244def ANY(subquery):
1245 return Subquery("ANY", subquery)
1246
1247def ALL(subquery):
1248 return Subquery("ALL", subquery)
1249
1250
1251
1252class ImportProxyField(SQLObjectField):
1253 def tablesUsedImmediate(self):
1254 return [str(self.tableName)]
1255
1256class ImportProxy(SQLExpression):
1257 '''Class to be used in column definitions that rely on other tables that might
1258 not yet be in a classregistry.
1259 '''
1260 FieldClass = ImportProxyField
1261 def __init__(self, clsName, registry=None):
1262 self.tableName = _DelayClass(self, clsName)
1263 self.sqlmeta = _Delay_proxy(table=_DelayClass(self, clsName))
1264 self.q = self
1265 self.soClass = None
1266 classregistry.registry(registry).addClassCallback(clsName, lambda foreign, me: setattr(me, 'soClass', foreign), self)
1267
1268 def __nonzero__(self):
1269 return True
1270
1271 def __getattr__(self, attr):
1272 if self.soClass is None:
1273 return _Delay(self, attr)
1274 return getattr(self.soClass.q, attr)
1275
1276class _Delay(SQLExpression):
1277 def __init__(self, proxy, attr):
1278 self.attr = attr
1279 self.proxy = proxy
1280
1281 def __sqlrepr__(self, db):
1282 if self.proxy.soClass is None:
1283 return '_DELAYED_' + self.attr
1284 val = self._resolve()
1285 if isinstance(val, SQLExpression):
1286 val = sqlrepr(val, db)
1287 return val
1288
1289 def tablesUsedImmediate(self):
1290 return getattr(self._resolve(), 'tablesUsedImmediate', lambda: [])()
1291
1292 def components(self):
1293 return getattr(self._resolve(), 'components', lambda: [])()
1294
1295 def _resolve(self):
1296 return getattr(self.proxy, self.attr)
1297
1298
1299 def fieldName(self):
1300 class _aliasFieldName(SQLExpression):
1301 def __init__(self, proxy):
1302 self.proxy = proxy
1303 def __sqlrepr__(self, db):
1304 return self.proxy._resolve().fieldName
1305 return _aliasFieldName(self)
1306 fieldName = property(fieldName)
1307
1308class _DelayClass(_Delay):
1309 def _resolve(self):
1310 return self.proxy.soClass.sqlmeta.table
1311
1312class _Delay_proxy(object):
1313 def __init__(self, **kw):
1314 self.__dict__.update(kw)
1315
1316
1317
1318
1319
1320
1321
1322
1323table = TableSpace()
1324const = ConstantSpace()
1325func = const
1326
1327
1328
1329
1330
1331if __name__ == "__main__":
1332 tests = """
1333>>> AND(table.address.name == "Ian Bicking", table.address.zip > 30000)
1334>>> table.address.name
1335>>> AND(LIKE(table.address.name, "this"), IN(table.address.zip, [100, 200, 300]))
1336>>> Select([table.address.name, table.address.state], where=LIKE(table.address.name, "%ian%"))
1337>>> Select([table.user.name], where=AND(table.user.state == table.states.abbrev))
1338>>> Insert(table.address, [{"name": "BOB", "address": "3049 N. 18th St."}, {"name": "TIM", "address": "409 S. 10th St."}])
1339>>> Insert(table.address, [("BOB", "3049 N. 18th St."), ("TIM", "409 S. 10th St.")], template=('name', 'address'))
1340>>> Delete(table.address, where="BOB"==table.address.name)
1341>>> Update(table.address, {"lastModified": const.NOW()})
1342>>> Replace(table.address, [("BOB", "3049 N. 18th St."), ("TIM", "409 S. 10th St.")], template=('name', 'address'))
1343"""
1344 for expr in tests.split('\n'):
1345 if not expr.strip(): continue
1346 if expr.startswith('>>> '):
1347 expr = expr[4:]