sqlbuilder.py :  » Database » SQLObject » SQLObject-0.12.4 » sqlobject » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » SQLObject 
SQLObject » SQLObject 0.12.4 » sqlobject » sqlbuilder.py
"""
sqlobject.sqlbuilder
--------------------

:author: Ian Bicking <ianb@colorstudy.com>

Builds SQL expressions from normal Python expressions.

Disclaimer
----------

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.

Instructions
------------

To begin a SQL expression, you must use some sort of SQL object -- a
field, table, or SQL statement (``SELECT``, ``INSERT``, etc.)  You can
then use normal operators, with the exception of: `and`, `or`, `not`,
and `in`.  You can use the `AND`, `OR`, `NOT`, and `IN` functions
instead, or you can also use `&`, `|`, and `~` for `and`, `or`, and
`not` respectively (however -- the precidence for these operators
doesn't work as you would want, so you must use many parenthesis).

To create a sql field, table, or constant/function, use the namespaces
`table`, `const`, and `func`.  For instance, ``table.address`` refers
to the ``address`` table, and ``table.address.state`` refers to the
``state`` field in the address table.  ``const.NULL`` is the ``NULL``
SQL constant, and ``func.NOW()`` is the ``NOW()`` function call
(`const` and `func` are actually identicle, but the two names are
provided for clarity).  Once you create this object, expressions
formed with it will produce SQL statements.

The ``sqlrepr(obj)`` function gets the SQL representation of these
objects, as well as the proper SQL representation of basic Python
types (None==NULL).

There are a number of DB-specific SQL features that this does not
implement.  There are a bunch of normal ANSI features also not present
-- particularly left joins, among others.  You may wish to only use
this to generate ``WHERE`` clauses.

See the bottom of this module for some examples, and run it (i.e.
``python sql.py``) to see the results of those examples.

"""

########################################
## Constants
########################################

import fnmatch
import operator
import re
import threading
import types
import weakref

import classregistry
from converters import sqlrepr,registerConverter


class VersionError(Exception):
    pass
class NoDefault:
    pass


class SQLObjectState(object):
    def __init__(self, soObject):
        self.soObject = weakref.proxy(soObject)


safeSQLRE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_\.]*$')
def sqlIdentifier(obj):
    # some db drivers return unicode column names
    return isinstance(obj, basestring) and bool(safeSQLRE.search(obj.strip()))


def execute(expr, executor):
    if hasattr(expr, 'execute'):
        return expr.execute(executor)
    else:
        return expr

########################################
## Expression generation
########################################

class SQLExpression:
    def __add__(self, other):
        return SQLOp("+", self, other)
    def __radd__(self, other):
        return SQLOp("+", other, self)
    def __sub__(self, other):
        return SQLOp("-", self, other)
    def __rsub__(self, other):
        return SQLOp("-", other, self)
    def __mul__(self, other):
        return SQLOp("*", self, other)
    def __rmul__(self, other):
        return SQLOp("*", other, self)
    def __div__(self, other):
        return SQLOp("/", self, other)
    def __rdiv__(self, other):
        return SQLOp("/", other, self)
    def __pos__(self):
        return SQLPrefix("+", self)
    def __neg__(self):
        return SQLPrefix("-", self)
    def __pow__(self, other):
        return SQLConstant("POW")(self, other)
    def __rpow__(self, other):
        return SQLConstant("POW")(other, self)
    def __abs__(self):
        return SQLConstant("ABS")(self)
    def __mod__(self, other):
        return SQLConstant("MOD")(self, other)
    def __rmod__(self, other):
        return SQLConstant("MOD")(other, self)

    def __lt__(self, other):
        return SQLOp("<", self, other)
    def __le__(self, other):
        return SQLOp("<=", self, other)
    def __gt__(self, other):
        return SQLOp(">", self, other)
    def __ge__(self, other):
        return SQLOp(">=", self, other)
    def __eq__(self, other):
        if other is None:
            return ISNULL(self)
        else:
            return SQLOp("=", self, other)
    def __ne__(self, other):
        if other is None:
            return ISNOTNULL(self)
        else:
            return SQLOp("<>", self, other)

    def __and__(self, other):
        return SQLOp("AND", self, other)
    def __rand__(self, other):
        return SQLOp("AND", other, self)
    def __or__(self, other):
        return SQLOp("OR", self, other)
    def __ror__(self, other):
        return SQLOp("OR", other, self)
    def __invert__(self):
        return SQLPrefix("NOT", self)

    def __call__(self, *args):
        return SQLCall(self, args)

    def __repr__(self):
        try:
            return self.__sqlrepr__(None)
        except AssertionError:
            return '<%s %s>' % (
                self.__class__.__name__, hex(id(self))[2:])

    def __str__(self):
        return repr(self)

    def __cmp__(self, other):
        raise VersionError, "Python 2.1+ required"
    def __rcmp__(self, other):
        raise VersionError, "Python 2.1+ required"

    def startswith(self, s):
        return STARTSWITH(self, s)
    def endswith(self, s):
        return ENDSWITH(self, s)
    def contains(self, s):
        return CONTAINSSTRING(self, s)

    def components(self):
        return []

    def tablesUsed(self, db):
        return self.tablesUsedSet(db)
    def tablesUsedSet(self, db):
        tables = set()
        for table in self.tablesUsedImmediate():
            if hasattr(table, '__sqlrepr__'):
                table = sqlrepr(table, db)
            tables.add(table)
        for component in self.components():
            tables.update(tablesUsedSet(component, db))
        return tables
    def tablesUsedImmediate(self):
        return []

#######################################
# Converter for SQLExpression instances
#######################################

def SQLExprConverter(value, db):
    return value.__sqlrepr__()

registerConverter(SQLExpression, SQLExprConverter)

def tablesUsedSet(obj, db):
    if hasattr(obj, "tablesUsedSet"):
        return obj.tablesUsedSet(db)
    else:
        return {}

operatorMap = {
    "+": operator.add,
    "/": operator.div,
    "-": operator.sub,
    "*": operator.mul,
    "<": operator.lt,
    "<=": operator.le,
    "=": operator.eq,
    "!=": operator.ne,
    ">=": operator.ge,
    ">": operator.gt,
    "IN": operator.contains,
    "IS": operator.eq,
    }

class SQLOp(SQLExpression):
    def __init__(self, op, expr1, expr2):
        self.op = op.upper()
        self.expr1 = expr1
        self.expr2 = expr2
    def __sqlrepr__(self, db):
        s1 = sqlrepr(self.expr1, db)
        s2 = sqlrepr(self.expr2, db)
        if s1[0] != '(' and s1 != 'NULL':
            s1 = '(' + s1 + ')'
        if s2[0] != '(' and s2 != 'NULL':
            s2 = '(' + s2 + ')'
        return "(%s %s %s)" % (s1, self.op, s2)
    def components(self):
        return [self.expr1, self.expr2]
    def execute(self, executor):
        if self.op == "AND":
            return execute(self.expr1, executor) \
                   and execute(self.expr2, executor)
        elif self.op == "OR":
            return execute(self.expr1, executor) \
                   or execute(self.expr2, executor)
        else:
            return operatorMap[self.op.upper()](execute(self.expr1, executor),
                                                execute(self.expr2, executor))

registerConverter(SQLOp, SQLExprConverter)

class SQLCall(SQLExpression):
    def __init__(self, expr, args):
        self.expr = expr
        self.args = args
    def __sqlrepr__(self, db):
        return "%s%s" % (sqlrepr(self.expr, db), sqlrepr(self.args, db))
    def components(self):
        return [self.expr] + list(self.args)
    def execute(self, executor):
        raise ValueError, "I don't yet know how to locally execute functions"

registerConverter(SQLCall, SQLExprConverter)

class SQLPrefix(SQLExpression):
    def __init__(self, prefix, expr):
        self.prefix = prefix
        self.expr = expr
    def __sqlrepr__(self, db):
        return "%s %s" % (self.prefix, sqlrepr(self.expr, db))
    def components(self):
        return [self.expr]
    def execute(self, executor):
        expr = execute(self.expr, executor)
        if prefix == "+":
            return expr
        elif prefix == "-":
            return -expr
        elif prefix.upper() == "NOT":
            return not expr

registerConverter(SQLPrefix, SQLExprConverter)

class SQLConstant(SQLExpression):
    def __init__(self, const):
        self.const = const
    def __sqlrepr__(self, db):
        return self.const
    def execute(self, executor):
        raise ValueError, "I don't yet know how to execute SQL constants"

registerConverter(SQLConstant, SQLExprConverter)

class SQLTrueClauseClass(SQLExpression):
    def __sqlrepr__(self, db):
        return "1 = 1"
    def execute(self, executor):
        return 1

SQLTrueClause = SQLTrueClauseClass()

registerConverter(SQLTrueClauseClass, SQLExprConverter)

########################################
## Namespaces
########################################

class Field(SQLExpression):
    def __init__(self, tableName, fieldName):
        self.tableName = tableName
        self.fieldName = fieldName
    def __sqlrepr__(self, db):
        return self.tableName + "." + self.fieldName
    def tablesUsedImmediate(self):
        return [self.tableName]
    def execute(self, executor):
        return executor.field(self.tableName, self.fieldName)

class SQLObjectField(Field):
    def __init__(self, tableName, fieldName, original, soClass, column):
        Field.__init__(self, tableName, fieldName)
        self.original = original
        self.soClass = soClass
        self.column = column
    def _from_python(self, value):
        column = self.column
        if not isinstance(value, SQLExpression) and column and column.from_python:
            value = column.from_python(value, SQLObjectState(self.soClass))
        return value
    def __eq__(self, other):
        if other is None:
            return ISNULL(self)
        other = self._from_python(other)
        return SQLOp('=', self, other)
    def __ne__(self, other):
        if other is None:
            return ISNOTNULL(self)
        other = self._from_python(other)
        return SQLOp('<>', self, other)
    def startswith(self, s):
        s = self._from_python(s)
        return STARTSWITH(self, s)
    def endswith(self, s):
        s = self._from_python(s)
        return ENDSWITH(self, s)
    def contains(self, s):
        s = self._from_python(s)
        return CONTAINSSTRING(self, s)

registerConverter(SQLObjectField, SQLExprConverter)


class Table(SQLExpression):
    FieldClass = Field

    def __init__(self, tableName):
        self.tableName = tableName
    def __getattr__(self, attr):
        if attr.startswith('__'):
            raise AttributeError
        return self.FieldClass(self.tableName, attr)
    def __sqlrepr__(self, db):
        if isinstance(self.tableName, str):
            return self.tableName
        return sqlrepr(self.tableName, db)
    def execute(self, executor):
        raise ValueError, "Tables don't have values"

class SQLObjectTable(Table):
    FieldClass = SQLObjectField

    def __init__(self, soClass):
        self.soClass = soClass
        assert soClass.sqlmeta.table, (
            "Bad table name in class %r: %r"
            % (soClass, soClass.sqlmeta.table))
        Table.__init__(self, soClass.sqlmeta.table)

    def __getattr__(self, attr):
        if attr.startswith('__'):
            raise AttributeError
        if attr == 'id':
            return self._getattrFromID(attr)
        elif attr in self.soClass.sqlmeta.columns:
            column = self.soClass.sqlmeta.columns[attr]
            return self._getattrFromColumn(column, attr)
        elif attr+'ID' in [k for (k, v) in self.soClass.sqlmeta.columns.items() if v.foreignKey]:
            attr += 'ID'
            column = self.soClass.sqlmeta.columns[attr]
            return self._getattrFromColumn(column, attr)
        else:
            raise AttributeError("%s instance has no attribute '%s'" % (self.soClass.__name__, attr))

    def _getattrFromID(self, attr):
        return self.FieldClass(self.tableName, self.soClass.sqlmeta.idName, attr, self.soClass, None)

    def _getattrFromColumn(self, column, attr):
        return self.FieldClass(self.tableName, column.dbName, attr, self.soClass, column)

class SQLObjectTableWithJoins(SQLObjectTable):

    def __getattr__(self, attr):
        if attr+'ID' in [k for (k,v) in self.soClass.sqlmeta.columns.items() if v.foreignKey]:
            column = self.soClass.sqlmeta.columns[attr+'ID']
            return self._getattrFromForeignKey(column, attr)
        elif attr in [x.joinMethodName for x in self.soClass.sqlmeta.joins]:
            join = [x for x in self.soClass.sqlmeta.joins if x.joinMethodName == attr][0]
            return self._getattrFromJoin(join, attr)
        else:
            return SQLObjectTable.__getattr__(self, attr)

    def _getattrFromForeignKey(self, column, attr):
        ret =  getattr(self, column.name) == \
              getattr(self.soClass, '_SO_class_'+column.foreignKey).q.id
        return ret

    def _getattrFromJoin(self, join, attr):
        if hasattr(join, 'otherColumn'):
            return AND(join.otherClass.q.id == Field(join.intermediateTable, join.otherColumn),
                            Field(join.intermediateTable, join.joinColumn) == self.soClass.q.id)
        else:
            return getattr(join.otherClass.q, join.joinColumn)==self.soClass.q.id

class TableSpace:
    TableClass = Table

    def __getattr__(self, attr):
        if attr.startswith('__'):
            raise AttributeError
        return self.TableClass(attr)

class ConstantSpace:
    def __getattr__(self, attr):
        if attr.startswith('__'):
            raise AttributeError
        return SQLConstant(attr)


########################################
## Table aliases
########################################

class AliasField(Field):
    def __init__(self, tableName, fieldName, alias, aliasTable):
        Field.__init__(self, tableName, fieldName)
        self.alias = alias
        self.aliasTable = aliasTable

    def __sqlrepr__(self, db):
        fieldName = self.fieldName
        if isinstance(fieldName, SQLExpression):
            fieldName = sqlrepr(fieldName, db)
        return self.alias + "." + fieldName

    def tablesUsedImmediate(self):
        return [self.aliasTable]

class AliasTable(Table):
    as_string = '' # set it to "AS" if your database requires it
    FieldClass = AliasField

    _alias_lock = threading.Lock()
    _alias_counter = 0

    def __init__(self, table, alias=None):
        if hasattr(table, "sqlmeta"):
            tableName = SQLConstant(table.sqlmeta.table)
        elif isinstance(table, (Select,Union)):
            assert alias is not None, "Alias name cannot be constructed from Select instances, please provide 'alias' kw."
            tableName = Subquery('', table)
            table = None
        else:
            tableName = SQLConstant(table)
            table = None
        Table.__init__(self, tableName)
        self.table = table
        if alias is None:
            self._alias_lock.acquire()
            try:
                AliasTable._alias_counter += 1
                alias = "%s_alias%d" % (tableName, AliasTable._alias_counter)
            finally:
                self._alias_lock.release()
        self.alias = alias

    def __getattr__(self, attr):
        if attr.startswith('__'):
            raise AttributeError
        if self.table:
            attr = getattr(self.table.q, attr).fieldName
        return self.FieldClass(self.tableName, attr, self.alias, self)

    def __sqlrepr__(self, db):
        return "%s %s %s" % (sqlrepr(self.tableName, db), self.as_string, self.alias)

class Alias(SQLExpression):
    def __init__(self, table, alias=None):
        self.q = AliasTable(table, alias)

    def __sqlrepr__(self, db):
        return sqlrepr(self.q, db)

    def components(self):
        return [self.q]


class Union(SQLExpression):
    def __init__(self, *tables):
        tabs = []
        for t in tables:
            if not isinstance(t, SQLExpression) and hasattr(t, 'sqlmeta'):
                t = t.sqlmeta.table
                if isinstance(t, Alias):
                    t = t.q
                if isinstance(t, Table):
                    t = t.tableName
                if not isinstance(t, SQLExpression):
                    t = SQLConstant(t.sqlmeta.table)
            tabs.append(t)
        self.tables = tabs

    def __sqlrepr__(self, db):
        return " UNION ".join([str(sqlrepr(t, db)) for t in self.tables])

########################################
## SQL Statements
########################################

class Select(SQLExpression):
    def __init__(self, items=NoDefault, where=NoDefault, groupBy=NoDefault,
                 having=NoDefault, orderBy=NoDefault, limit=NoDefault,
                 join=NoDefault, lazyColumns=False, distinct=False,
                 start=0, end=None, reversed=False, forUpdate=False,
                 clause=NoDefault, staticTables=NoDefault, distinctOn=NoDefault):
        self.ops = {}
        if not isinstance(items, (list, tuple, types.GeneratorType)):
            items = [items]
        if clause is NoDefault and where is not NoDefault:
            clause = where
        if staticTables is NoDefault:
            staticTables = []
        self.ops['items'] = items
        self.ops['clause'] = clause
        self.ops['groupBy'] = groupBy
        self.ops['having'] = having
        self.ops['orderBy'] = orderBy
        self.ops['limit'] = limit
        self.ops['join']  = join
        self.ops['lazyColumns'] = lazyColumns
        self.ops['distinct'] = distinct
        self.ops['distinctOn'] = distinctOn
        self.ops['start'] = start
        self.ops['end'] = end
        self.ops['reversed'] = reversed
        self.ops['forUpdate'] = forUpdate
        self.ops['staticTables'] = staticTables

    def clone(self, **newOps):
        ops = self.ops.copy()
        ops.update(newOps)
        return self.__class__(**ops)

    def newItems(self, items):
        return self.clone(items=items)

    def newClause(self, new_clause):
        return self.clone(clause=new_clause)

    def orderBy(self, orderBy):
        return self.clone(orderBy=orderBy)

    def unlimited(self):
        return self.clone(limit=NoDefault, start=0, end=None)

    def limit(self, limit):
        self.clone(limit=limit)

    def lazyColumns(self, value):
        return self.clone(lazyColumns=value)

    def reversed(self):
        return self.clone(reversed=not self.ops.get('reversed', False))

    def distinct(self):
        return self.clone(distinct=True)

    def filter(self, filter_clause):
        if filter_clause is None:
            # None doesn't filter anything, it's just a no-op:
            return self
        clause = self.ops['clause']
        if isinstance(clause, basestring):
            clause = SQLConstant('(%s)' % clause)

        if clause == SQLTrueClause:
            newClause = filter_clause
        else:
            newClause = AND(clause, filter_clause)
        return self.newClause(newClause)

    def __sqlrepr__(self, db):

        select = "SELECT"
        if self.ops['distinct']:
            select += " DISTINCT"
            if self.ops['distinctOn'] is not NoDefault:
                select += " ON(%s)" % sqlrepr(self.ops['distinctOn'], db)
        if not self.ops['lazyColumns']:
            select += " %s" % ", ".join([str(sqlrepr(v, db)) for v in self.ops['items']])
        else:
            select += " %s" % sqlrepr(self.ops['items'][0], db)

        join = []
        join_str = ''
        if self.ops['join'] is not NoDefault and self.ops['join'] is not None:
            _join = self.ops['join']
            if isinstance(_join, str):
                join_str = " " + _join
            elif isinstance(_join, SQLJoin):
                join.append(_join)
            else:
                join.extend(_join)
        tables = set()
        for x in self.ops['staticTables']:
            if isinstance(x, SQLExpression):
                x = sqlrepr(x, db)
            tables.add(x)
        things = list(self.ops['items']) + join
        if self.ops['clause'] is not NoDefault:
            things.append(self.ops['clause'])
        for thing in things:
            if isinstance(thing, SQLExpression):
                tables.update(tablesUsedSet(thing, db))
        for j in join:
            t1, t2 = sqlrepr(j.table1, db), sqlrepr(j.table2, db)
            if t1 in tables: tables.remove(t1)
            if t2 in tables: tables.remove(t2)
        if tables:
            select += " FROM %s" % ", ".join(tables)
        elif join:
            select += " FROM"
        tablesYet = tables
        for j in join:
            if tablesYet and j.table1:
                sep = ", "
            else:
                sep = " "
            select += sep + sqlrepr(j, db)
            tablesYet = True

        if join_str:
            select += join_str

        if self.ops['clause'] is not NoDefault:
            select += " WHERE %s" % sqlrepr(self.ops['clause'], db)
        if self.ops['groupBy'] is not NoDefault:
            groupBy = sqlrepr(self.ops['groupBy'], db)
            if isinstance(self.ops['groupBy'], (list, tuple)):
                groupBy = groupBy[1:-1] # Remove parens
            select += " GROUP BY %s" % groupBy
        if self.ops['having'] is not NoDefault:
            select += " HAVING %s" % sqlrepr(self.ops['having'], db)
        if self.ops['orderBy'] is not NoDefault and self.ops['orderBy'] is not None:
            orderBy = self.ops['orderBy']
            if self.ops['reversed']:
                reverser = DESC
            else:
                reverser = lambda x: x
            if isinstance(orderBy, (list, tuple)):
                select += " ORDER BY %s" % ", ".join([sqlrepr(reverser(x), db) for x in orderBy])
            else:
                select += " ORDER BY %s" % sqlrepr(reverser(orderBy), db)
        start, end = self.ops['start'], self.ops['end']
        if self.ops['limit'] is not NoDefault:
            end = start + self.ops['limit']
        if start or end:
            from dbconnection import dbConnectionForScheme
            select = dbConnectionForScheme(db)._queryAddLimitOffset(select, start, end)
        if self.ops['forUpdate']:
            select += " FOR UPDATE"
        return select

registerConverter(Select, SQLExprConverter)

class Insert(SQLExpression):
    def __init__(self, table, valueList=None, values=None, template=NoDefault):
        self.template = template
        self.table = table
        if valueList:
            if values:
                raise TypeError, "You may only give valueList *or* values"
            self.valueList = valueList
        else:
            self.valueList = [values]
    def __sqlrepr__(self, db):
        if not self.valueList:
            return ''
        insert = "INSERT INTO %s" % self.table
        allowNonDict = True
        template = self.template
        if (template is NoDefault) and isinstance(self.valueList[0], dict):
            template = self.valueList[0].keys()
            allowNonDict = False
        if template is not NoDefault:
            insert += " (%s)" % ", ".join(template)
        insert += " VALUES "
        listToJoin = []
        listToJoin_app = listToJoin.append
        for value in self.valueList:
            if isinstance(value, dict):
                if template is NoDefault:
                    raise TypeError, "You can't mix non-dictionaries with dictionaries in an INSERT if you don't provide a template (%s)" % repr(value)
                value = dictToList(template, value)
            elif not allowNonDict:
                raise TypeError, "You can't mix non-dictionaries with dictionaries in an INSERT if you don't provide a template (%s)" % repr(value)
            listToJoin_app("(%s)" % ", ".join([sqlrepr(v, db) for v in value]))
        insert = "%s%s" % (insert, ", ".join(listToJoin))
        return insert

registerConverter(Insert, SQLExprConverter)

def dictToList(template, dict):
    list = []
    for key in template:
        list.append(dict[key])
    if len(dict.keys()) > len(template):
        raise TypeError, "Extra entries in dictionary that aren't asked for in template (template=%s, dict=%s)" % (repr(template), repr(dict))
    return list

class Update(SQLExpression):
    def __init__(self, table, values, template=NoDefault, where=NoDefault):
        self.table = table
        self.values = values
        self.template = template
        self.whereClause = where
    def __sqlrepr__(self, db):
        update = "%s %s" % (self.sqlName(), self.table)
        update += " SET"
        first = True
        if self.template is not NoDefault:
            for i in range(len(self.template)):
                if first:
                    first = False
                else:
                    update += ","
                update += " %s=%s" % (self.template[i], sqlrepr(self.values[i], db))
        else:
            for key, value in self.values.items():
                if first:
                    first = False
                else:
                    update += ","
                update += " %s=%s" % (key, sqlrepr(value, db))
        if self.whereClause is not NoDefault:
            update += " WHERE %s" % sqlrepr(self.whereClause, db)
        return update
    def sqlName(self):
        return "UPDATE"

registerConverter(Update, SQLExprConverter)

class Delete(SQLExpression):
    """To be safe, this will signal an error if there is no where clause,
    unless you pass in where=None to the constructor."""
    def __init__(self, table, where=NoDefault):
        self.table = table
        if where is NoDefault:
            raise TypeError, "You must give a where clause or pass in None to indicate no where clause"
        self.whereClause = where
    def __sqlrepr__(self, db):
        whereClause = self.whereClause
        if whereClause is None:
            return "DELETE FROM %s" % self.table
        if isinstance(whereClause, SQLExpression):
            whereClause = sqlrepr(whereClause, db)
        return "DELETE FROM %s WHERE %s" % (self.table, whereClause)

registerConverter(Delete, SQLExprConverter)

class Replace(Update):
    def sqlName(self):
        return "REPLACE"

registerConverter(Replace, SQLExprConverter)

########################################
## SQL Builtins
########################################

class DESC(SQLExpression):

    def __init__(self, expr):
        self.expr = expr

    def __sqlrepr__(self, db):
        if isinstance(self.expr, DESC):
            return sqlrepr(self.expr.expr, db)
        return '%s DESC' % sqlrepr(self.expr, db)

def AND(*ops):
    if not ops:
        return None
    op1 = ops[0]
    ops = ops[1:]
    if ops:
        return SQLOp("AND", op1, AND(*ops))
    else:
        return op1

def OR(*ops):
    if not ops:
        return None
    op1 = ops[0]
    ops = ops[1:]
    if ops:
        return SQLOp("OR", op1, OR(*ops))
    else:
        return op1

def NOT(op):
    return SQLPrefix("NOT", op)

def _IN(item, list):
    return SQLOp("IN", item, list)

def IN(item, list):
    if isinstance(list, Select):
        return INSubquery(item, list)
    else:
        return _IN(item, list)

def NOTIN(item, list):
    if isinstance(list, Select):
        return NOTINSubquery(item, list)
    else:
        return NOT(_IN(item, list))

def STARTSWITH(expr, pattern):
    return LIKE(expr, _LikeQuoted(pattern) + '%', escape='\\')

def ENDSWITH(expr, pattern):
    return LIKE(expr, '%' + _LikeQuoted(pattern), escape='\\')

def CONTAINSSTRING(expr, pattern):
    return LIKE(expr, '%' + _LikeQuoted(pattern) + '%', escape='\\')

def ISNULL(expr):
    return SQLOp("IS", expr, None)

def ISNOTNULL(expr):
    return SQLOp("IS NOT", expr, None)

class ColumnAS(SQLOp):
    ''' Just like SQLOp('AS', expr, name) except without the parentheses '''
    def __init__(self, expr, name):
        if isinstance(name, basestring):
            name = SQLConstant(name)
        SQLOp.__init__(self, 'AS', expr, name)
    def __sqlrepr__(self, db):
        return "%s %s %s" % (sqlrepr(self.expr1, db), self.op, sqlrepr(self.expr2, db))

class _LikeQuoted:
    # It assumes prefix and postfix are strings; usually just a percent sign.

    # @@: I'm not sure what the quoting rules really are for all the
    # databases

    def __init__(self, expr):
        self.expr = expr
        self.prefix = ''
        self.postfix = ''

    def __radd__(self, s):
        self.prefix = s + self.prefix
        return self

    def __add__(self, s):
        self.postfix += s
        return self

    def __sqlrepr__(self, db):
        s = self.expr
        if isinstance(s, SQLExpression):
            values = []
            if self.prefix:
                values.append("'%s'" % self.prefix)
            s = _quote_like_special(sqlrepr(s, db), db)
            values.append(s)
            if self.postfix:
                values.append("'%s'" % self.postfix)
            if db == "mysql":
                return "CONCAT(%s)" % ", ".join(values)
            else:
                return " || ".join(values)
        elif isinstance(s, basestring):
            s = _quote_like_special(sqlrepr(s, db)[1:-1], db)
            return "'%s%s%s'" % (self.prefix, s, self.postfix)
        else:
           raise TypeError, "expected str, unicode or SQLExpression, got %s" % type(s)

def _quote_like_special(s, db):
    if db == 'postgres':
        escape = r'\\'
    else:
        escape = '\\'
    s = s.replace('\\', r'\\').replace('%', escape+'%').replace('_', escape+'_')
    return s

########################################
## SQL JOINs
########################################

class SQLJoin(SQLExpression):
    def __init__(self, table1, table2, op=','):
        if hasattr(table1, 'sqlmeta'):
            table1 = table1.sqlmeta.table
        if hasattr(table2, 'sqlmeta'):
            table2 = table2.sqlmeta.table
        if isinstance(table1, str):
            table1 = SQLConstant(table1)
        if isinstance(table2, str):
            table2 = SQLConstant(table2)
        self.table1 = table1
        self.table2 = table2
        self.op = op

    def __sqlrepr__(self, db):
        if self.table1:
            return "%s%s %s" % (sqlrepr(self.table1, db), self.op, sqlrepr(self.table2, db))
        else:
            return "%s %s" % (self.op, sqlrepr(self.table2, db))

registerConverter(SQLJoin, SQLExprConverter)

def JOIN(table1, table2):
    return SQLJoin(table1, table2, " JOIN")

def INNERJOIN(table1, table2):
    return SQLJoin(table1, table2, " INNER JOIN")

def CROSSJOIN(table1, table2):
    return SQLJoin(table1, table2, " CROSS JOIN")

def STRAIGHTJOIN(table1, table2):
    return SQLJoin(table1, table2, " STRAIGHT JOIN")

def LEFTJOIN(table1, table2):
    return SQLJoin(table1, table2, " LEFT JOIN")

def LEFTOUTERJOIN(table1, table2):
    return SQLJoin(table1, table2, " LEFT OUTER JOIN")

def NATURALJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL JOIN")

def NATURALLEFTJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL LEFT JOIN")

def NATURALLEFTOUTERJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL LEFT OUTER JOIN")

def RIGHTJOIN(table1, table2):
    return SQLJoin(table1, table2, " RIGHT JOIN")

def RIGHTOUTERJOIN(table1, table2):
    return SQLJoin(table1, table2, " RIGHT OUTER JOIN")

def NATURALRIGHTJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL RIGHT JOIN")

def NATURALRIGHTOUTERJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL RIGHT OUTER JOIN")

def FULLJOIN(table1, table2):
    return SQLJoin(table1, table2, " FULL JOIN")

def FULLOUTERJOIN(table1, table2):
    return SQLJoin(table1, table2, " FULL OUTER JOIN")

def NATURALFULLJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL FULL JOIN")

def NATURALFULLOUTERJOIN(table1, table2):
    return SQLJoin(table1, table2, " NATURAL FULL OUTER JOIN")

class SQLJoinConditional(SQLJoin):
    """Conditional JOIN"""
    def __init__(self, table1, table2, op, on_condition=None, using_columns=None):
        """For condition you must give on_condition or using_columns but not both

            on_condition can be a string or SQLExpression, for example
                Table1.q.col1 == Table2.q.col2
            using_columns can be a string or a list of columns, e.g.
                (Table1.q.col1, Table2.q.col2)
        """
        if not on_condition and not using_columns:
            raise TypeError, "You must give ON condition or USING columns"
        if on_condition and using_columns:
            raise TypeError, "You must give ON condition or USING columns but not both"
        SQLJoin.__init__(self, table1, table2, op)
        self.on_condition = on_condition
        self.using_columns = using_columns

    def __sqlrepr__(self, db):
        if self.on_condition:
            on_condition = self.on_condition
            if hasattr(on_condition, "__sqlrepr__"):
                on_condition = sqlrepr(on_condition, db)
            join = "%s %s ON %s" % (self.op, sqlrepr(self.table2, db), on_condition)
            if self.table1:
                join = "%s %s" % (sqlrepr(self.table1, db), join)
            return join
        elif self.using_columns:
            using_columns = []
            for col in self.using_columns:
                if hasattr(col, "__sqlrepr__"):
                    col = sqlrepr(col, db)
                using_columns.append(col)
            using_columns = ", ".join(using_columns)
            join = "%s %s USING (%s)" % (self.op, sqlrepr(self.table2, db), using_columns)
            if self.table1:
                join = "%s %s" % (sqlrepr(self.table1, db), join)
            return join
        else:
            RuntimeError, "Impossible error"

registerConverter(SQLJoinConditional, SQLExprConverter)

def INNERJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "INNER JOIN", on_condition, using_columns)

def LEFTJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "LEFT JOIN", on_condition, using_columns)

def LEFTOUTERJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "LEFT OUTER JOIN", on_condition, using_columns)

def RIGHTJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "RIGHT JOIN", on_condition, using_columns)

def RIGHTOUTERJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "RIGHT OUTER JOIN", on_condition, using_columns)

def FULLJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "FULL JOIN", on_condition, using_columns)

def FULLOUTERJOINConditional(table1, table2, on_condition=None, using_columns=None):
    return SQLJoinConditional(table1, table2, "FULL OUTER JOIN", on_condition, using_columns)

class SQLJoinOn(SQLJoinConditional):
    """Conditional JOIN ON"""
    def __init__(self, table1, table2, op, on_condition):
        SQLJoinConditional.__init__(self, table1, table2, op, on_condition)

registerConverter(SQLJoinOn, SQLExprConverter)

class SQLJoinUsing(SQLJoinConditional):
    """Conditional JOIN USING"""
    def __init__(self, table1, table2, op, using_columns):
        SQLJoinConditional.__init__(self, table1, table2, op, None, using_columns)

registerConverter(SQLJoinUsing, SQLExprConverter)

def INNERJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "INNER JOIN", on_condition)

def LEFTJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "LEFT JOIN", on_condition)

def LEFTOUTERJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "LEFT OUTER JOIN", on_condition)

def RIGHTJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "RIGHT JOIN", on_condition)

def RIGHTOUTERJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "RIGHT OUTER JOIN", on_condition)

def FULLJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "FULL JOIN", on_condition)

def FULLOUTERJOINOn(table1, table2, on_condition):
    return SQLJoinOn(table1, table2, "FULL OUTER JOIN", on_condition)

def INNERJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "INNER JOIN", using_columns)

def LEFTJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "LEFT JOIN", using_columns)

def LEFTOUTERJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "LEFT OUTER JOIN", using_columns)

def RIGHTJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "RIGHT JOIN", using_columns)

def RIGHTOUTERJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "RIGHT OUTER JOIN", using_columns)

def FULLJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "FULL JOIN", using_columns)

def FULLOUTERJOINUsing(table1, table2, using_columns):
    return SQLJoinUsing(table1, table2, "FULL OUTER JOIN", using_columns)


########################################
## Subqueries (subselects)
########################################

class OuterField(Field):
    def tablesUsedImmediate(self):
        return []

class OuterTable(Table):
    FieldClass = OuterField

    def __init__(self, table):
        if hasattr(table, "sqlmeta"):
            tableName = table.sqlmeta.table
        else:
            tableName = table
            table = None
        Table.__init__(self, tableName)
        self.table = table

class Outer:
    def __init__(self, table):
        self.q = OuterTable(table)


class LIKE(SQLExpression):
    op = "LIKE"

    def __init__(self, expr, string, escape=None):
        self.expr = expr
        self.string = string
        self.escape = escape
    def __sqlrepr__(self, db):
        escape = self.escape
        like = "%s %s (%s)" % (sqlrepr(self.expr, db), self.op, sqlrepr(self.string, db))
        if escape is None:
            return "(%s)" % like
        else:
            return "(%s ESCAPE %s)" % (like, sqlrepr(escape, db))
    def components(self):
        return [self.expr, self.string]
    def execute(self, executor):
        if not hasattr(self, '_regex'):
            # @@: Crude, not entirely accurate
            dest = self.string
            dest = dest.replace("%%", "\001")
            dest = dest.replace("*", "\002")
            dest = dest.replace("%", "*")
            dest = dest.replace("\001", "%")
            dest = dest.replace("\002", "[*]")
            self._regex = re.compile(fnmatch.translate(dest), re.I)
        return self._regex.search(execute(self.expr, executor))

class RLIKE(LIKE):
    op = "RLIKE"

    op_db = {
        'firebird': 'RLIKE',
        'maxdb': 'RLIKE',
        'mysql': 'RLIKE',
        'postgres': '~',
        'sqlite': 'REGEXP'
    }

    def _get_op(self, db):
        return self.op_db.get(db, 'LIKE')
    def __sqlrepr__(self, db):
        return "(%s %s (%s))" % (
            sqlrepr(self.expr, db), self._get_op(db), sqlrepr(self.string, db)
        )
    def execute(self, executor):
        self.op = self._get_op(self.db)
        return LIKE.execute(self, executor)


class INSubquery(SQLExpression):
    op = "IN"

    def __init__(self, item, subquery):
        self.item = item
        self.subquery = subquery
    def components(self):
        return [self.item]
    def __sqlrepr__(self, db):
        return "%s %s (%s)" % (sqlrepr(self.item, db), self.op, sqlrepr(self.subquery, db))

class NOTINSubquery(INSubquery):
    op = "NOT IN"


class Subquery(SQLExpression):
    def __init__(self, op, subquery):
        self.op = op
        self.subquery = subquery

    def __sqlrepr__(self, db):
        return "%s (%s)" % (self.op, sqlrepr(self.subquery, db))

def EXISTS(subquery):
    return Subquery("EXISTS", subquery)

def NOTEXISTS(subquery):
    return Subquery("NOT EXISTS", subquery)

def SOME(subquery):
    return Subquery("SOME", subquery)

def ANY(subquery):
    return Subquery("ANY", subquery)

def ALL(subquery):
    return Subquery("ALL", subquery)

####

class ImportProxyField(SQLObjectField):
    def tablesUsedImmediate(self):
        return [str(self.tableName)]

class ImportProxy(SQLExpression):
    '''Class to be used in column definitions that rely on other tables that might
        not yet be in a classregistry.
    '''
    FieldClass = ImportProxyField
    def __init__(self, clsName, registry=None):
        self.tableName = _DelayClass(self, clsName)
        self.sqlmeta = _Delay_proxy(table=_DelayClass(self, clsName))
        self.q = self
        self.soClass = None
        classregistry.registry(registry).addClassCallback(clsName,lambda foreign, me: setattr(me, 'soClass', foreign), self)

    def __nonzero__(self):
        return True

    def __getattr__(self, attr):
        if self.soClass is None:
            return _Delay(self, attr)
        return getattr(self.soClass.q, attr)

class _Delay(SQLExpression):
    def __init__(self, proxy, attr):
        self.attr = attr
        self.proxy = proxy

    def __sqlrepr__(self, db):
        if self.proxy.soClass is None:
            return '_DELAYED_' + self.attr
        val = self._resolve()
        if isinstance(val, SQLExpression):
            val = sqlrepr(val, db)
        return val

    def tablesUsedImmediate(self):
        return getattr(self._resolve(), 'tablesUsedImmediate', lambda: [])()

    def components(self):
        return getattr(self._resolve(), 'components', lambda: [])()

    def _resolve(self):
        return getattr(self.proxy, self.attr)

    # For AliasTable etc
    def fieldName(self):
        class _aliasFieldName(SQLExpression):
            def __init__(self, proxy):
                self.proxy = proxy
            def __sqlrepr__(self, db):
                return self.proxy._resolve().fieldName
        return _aliasFieldName(self)
    fieldName = property(fieldName)

class _DelayClass(_Delay):
    def _resolve(self):
        return self.proxy.soClass.sqlmeta.table

class _Delay_proxy(object):
    def __init__(self, **kw):
        self.__dict__.update(kw)

######


########################################
## Global initializations
########################################

table = TableSpace()
const = ConstantSpace()
func = const

########################################
## Testing
########################################

if __name__ == "__main__":
    tests = """
>>> AND(table.address.name == "Ian Bicking", table.address.zip > 30000)
>>> table.address.name
>>> AND(LIKE(table.address.name, "this"), IN(table.address.zip, [100, 200, 300]))
>>> Select([table.address.name, table.address.state], where=LIKE(table.address.name, "%ian%"))
>>> Select([table.user.name], where=AND(table.user.state == table.states.abbrev))
>>> Insert(table.address, [{"name": "BOB", "address": "3049 N. 18th St."}, {"name": "TIM", "address": "409 S. 10th St."}])
>>> Insert(table.address, [("BOB", "3049 N. 18th St."), ("TIM", "409 S. 10th St.")], template=('name', 'address'))
>>> Delete(table.address, where="BOB"==table.address.name)
>>> Update(table.address, {"lastModified": const.NOW()})
>>> Replace(table.address, [("BOB", "3049 N. 18th St."), ("TIM", "409 S. 10th St.")], template=('name', 'address'))
"""
    for expr in tests.split('\n'):
        if not expr.strip(): continue
        if expr.startswith('>>> '):
            expr = expr[4:]
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.