Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 135

Show
Ignore:
Timestamp:
08/13/07 06:14:24
Author:
fumanchu
Message:

Merged AST branch back into trunk.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/geniusql/__init__.py

    r134 r135  
    9999from geniusql import adapters 
    100100from geniusql import conns 
    101 from geniusql import decompil
     101from geniusql import depars
    102102from geniusql import isolation 
    103103from geniusql import select 
  • trunk/geniusql/adapters.py

    r134 r135  
    106106     
    107107    This must be performed in the Adapter (as opposed to the DatabaseType 
    108     or Decompiler) in order to support custom transformations like our 
     108    or Deparser) in order to support custom transformations like our 
    109109    date example, above: 
    110110     
     
    139139         
    140140        op1 and op2 will be SQLExpression objects. 
    141         op will be an index into opcode.cmp_op. Use it to switch 
     141        op will be a value from opcode.cmp_op. Use it to switch 
    142142            based on the operator (since sqlop will be provider-specific). 
    143143        sqlop will be the matching SQL for the given operator. 
     
    159159        # sqlite 3 will return an int. 
    160160        # This construction should handle both. 
     161        if value is None: 
     162            return None 
    161163        return bool(int(value)) 
    162164 
     
    172174     
    173175    def pull(self, value, dbtype): 
     176        if value is None: 
     177            return None 
    174178        return bool(value) 
    175179 
     
    188192     
    189193    def pull(self, value, dbtype): 
     194        if value is None: 
     195            return None 
    190196        if isinstance(value, datetime.datetime): 
    191197            return value 
     
    204210     
    205211    def pull(self, value, dbtype): 
     212        if value is None: 
     213            return None 
    206214        # These are in order for a reason: datetime is a subclass of date! 
    207215        if isinstance(value, datetime.datetime): 
     
    223231     
    224232    def pull(self, value, dbtype): 
     233        if value is None: 
     234            return None 
    225235        if isinstance(value, datetime.time): 
    226236            return value 
     
    251261     
    252262    def pull(self, value, dbtype): 
     263        if value is None: 
     264            return None 
    253265        days, seconds = divmod(long(value), 86400) 
    254266        return datetime.timedelta(int(days), int(seconds)) 
     
    265277     
    266278    def pull(self, value, dbtype): 
     279        if value is None: 
     280            return None 
    267281        return float(value) 
    268282 
     
    287301     
    288302    def pull(self, value, dbtype): 
     303        if value is None: 
     304            return None 
    289305        return self.pytype(value) 
    290306 
     
    315331     
    316332    def pull(self, value, dbtype): 
     333        if value is None: 
     334            return None 
    317335        if isinstance(value, unicode): 
    318336            return value.encode(dbtype.encoding) 
     
    324342     
    325343    def pull(self, value, dbtype): 
     344        if value is None: 
     345            return None 
    326346        if isinstance(value, unicode): 
    327347            return value 
     
    349369     
    350370    def pull(self, value, dbtype): 
     371        if value is None: 
     372            return None 
    351373        # Coerce to str for pickle.loads restriction. 
    352374        if isinstance(value, unicode): 
     
    357379 
    358380 
     381def normalize_decimal(value): 
     382    """Return the given decimal value, normalized for SQL. 
     383     
     384    Normalization is by stripping trailing zeros after the decimal point. 
     385    This is critical to allow comparisons between "1", "1.", and "1.0". 
     386    """ 
     387    value = str(value) 
     388    if "." in value: 
     389        value = value.rstrip('0') 
     390    else: 
     391        value += "." 
     392    return "'%s'" % value 
     393 
     394 
    359395class number_to_TEXT(Adapter): 
    360396    """Adapt a numeric Python type (int|long|float) to a TEXT dbtype.""" 
     
    371407     
    372408    def pull(self, value, dbtype): 
     409        if value is None: 
     410            return None 
    373411        return self.pytype(value) 
    374412     
     
    386424         
    387425        op1 and op2 will be SQLExpression objects. 
    388         op will be an index into opcode.cmp_op. 
     426        op will be a value from opcode.cmp_op. 
    389427        sqlop will be the matching SQL for the given operator. 
    390428        """ 
    391         if op not in ('=', '!='): 
     429        if sqlop not in ('=', '!='): 
    392430            raise TypeError("Numbers stored in TEXT columns cannot be " 
    393431                            "compared except for (in)equality.") 
    394         return "CStr(%s) %s CStr(%s)" % (op1.sql, op, op2.sql) 
     432        if op1.value is None: 
     433            val1 = op1.sql 
     434        else: 
     435            val1 = "'%s'" % op1.value 
     436        if op2.value is None: 
     437            val2 = op2.sql 
     438        else: 
     439            val2 = "'%s'" % op2.value 
     440        return "%s %s %s" % (val1, sqlop, val2) 
    395441 
    396442 
     
    406452        if issubclass(self.pytype, float): 
    407453            # Make sure we get all 17 decimal digits. 
    408             return repr(value) 
     454            return "'" + repr(value) + "'" 
    409455        return str(value) 
    410456     
    411457    def pull(self, value, dbtype): 
     458        if value is None: 
     459            return None 
    412460        return self.pytype(value) 
    413461 
     
    426474         
    427475        def pull(self, value, dbtype): 
     476            if value is None: 
     477                return None 
    428478            # pywin32 build 205 began support for returning 
    429479            # COM Currency objects as decimal objects. 
     
    437487            if value is None: 
    438488                return 'NULL' 
    439             return "'" + str(value) + "'" 
     489            return normalize_decimal(value) 
    440490         
    441491        def binary_op(self, op1, op, sqlop, op2): 
     
    443493         
    444494        def compare_op(self, op1, op, sqlop, op2): 
    445             if op not in ('=', '!='): 
     495            if sqlop not in ('=', '!='): 
    446496                raise TypeError("Numbers stored in TEXT columns cannot be " 
    447497                                "compared except for (in)equality.") 
    448             return "CStr(%s) %s CStr(%s)" % (op1.sql, op, op2.sql) 
     498            if op1.value is None: 
     499                val1 = op1.sql 
     500            else: 
     501                val1 = normalize_decimal(op1.value) 
     502            if op2.value is None: 
     503                val2 = op2.sql 
     504            else: 
     505                val2 = normalize_decimal(op2.value) 
     506            return "%s %s %s" % (val1, sqlop, val2) 
    449507     
    450508    class decimal_to_SQL92REAL(Adapter): 
     
    462520         
    463521        def pull(self, value, dbtype): 
     522            if value is None: 
     523                return None 
    464524            if isinstance(value, float): 
    465525                value = repr(value) 
     
    478538            return str(value) 
    479539        def pull(self, value, dbtype): 
     540            if value is None: 
     541                return None 
    480542            if (isinstance(value, basestring) or 
    481543                (typerefs.decimal and 
     
    498560            if value is None: 
    499561                return 'NULL' 
    500             return "'" + str(value) + "'" 
     562            if not isinstance(value, typerefs.fixedpoint.FixedPoint): 
     563                value = typerefs.fixedpoint.FixedPoint(value) 
     564            return normalize_decimal(value) 
    501565         
    502566        def binary_op(self, op1, op, sqlop, op2): 
     
    504568         
    505569        def compare_op(self, op1, op, sqlop, op2): 
    506             if op not in ('=', '!='): 
     570            if sqlop not in ('=', '!='): 
    507571                raise TypeError("Numbers stored in TEXT columns cannot be " 
    508572                                "compared except for (in)equality.") 
    509             return "CStr(%s) %s CStr(%s)" % (op1.sql, op, op2.sql) 
     573            if op1.value is None: 
     574                val1 = op1.sql 
     575            else: 
     576                val1 = normalize_decimal(op1.value) 
     577            if op2.value is None: 
     578                val2 = op2.sql 
     579            else: 
     580                val2 = normalize_decimal(op2.value) 
     581            return "%s %s %s" % (val1, sqlop, val2) 
    510582     
    511583    class fixedpoint_to_SQL92REAL(Adapter): 
     
    518590         
    519591        def pull(self, value, dbtype): 
     592            if value is None: 
     593                return None 
    520594            if isinstance(value, float): 
    521595                value = repr(value) 
  • trunk/geniusql/codewalk.py

    r134 r135  
    749749    def walk(self): 
    750750        self.stack = [] 
    751         self.newcode = [] 
    752751        self.targets = {} 
    753752         
     
    13461345            self.flag = None 
    13471346 
     1347 
    13481348del k, v 
    13491349 
  • trunk/geniusql/dbtypes.py

    r134 r135  
    4848    def default_adapter(self, pytype): 
    4949        """Return a default adapter instance for the given pytype, dbtype.""" 
    50         ptypes = [pytype, None] 
    51         for base in pytype.__bases__: 
    52             ptypes.append(base) 
    53          
    54         for p in ptypes: 
    55             if p in self.default_adapters: 
    56                 return self.default_adapters[p] 
    57          
    58         raise TypeError("%s has no default adapter for %s. Looked for: %s" % 
    59                         (self, pytype, ", ".join([repr(x) for x in ptypes]))) 
     50        # Use try for the common case where the pytype is in the dict. 
     51        try: 
     52            return self.default_adapters[pytype] 
     53        except KeyError: 
     54            defaults = self.default_adapters 
     55            if None in defaults: 
     56                return defaults[None] 
     57            for p in pytype.__bases__: 
     58                if p in defaults: 
     59                    return defaults[p] 
     60         
     61        raise TypeError("%s has no default adapter for %s. Looked for: " 
     62                        "%s, None, %s" % 
     63                        (self, pytype, pytype, 
     64                         ", ".join([repr(x) for x in ptype.__bases__]))) 
    6065 
    6166 
     
    279284 
    280285class SQL92SMALLINT(SQL92INTEGER): 
    281     # "The precision of SMALLINT shall be less than or 
    282     # equal to the precision of INTEGER." 
     286    """Base class for SQL 92 SMALLINT types. 
     287     
     288    "The precision of SMALLINT shall be less than or equal to the 
     289    precision of INTEGER." 
     290    """ 
    283291    _bytes = max_bytes = 2 
    284292 
    285293 
    286 # "FLOAT specifies the data type approximate numeric, with binary 
    287 #    precision equal to or greater than the value of the specified 
    288 #    <precision>. The maximum value of <precision> is implementation- 
    289 #    defined. <precision> shall not be greater than this value." 
    290  
    291294class SQL92FLOAT(AdjustablePrecisionType): 
    292     """Base class for SQL 92 FLOAT types.""" 
     295    """Base class for SQL 92 FLOAT types. 
     296     
     297    "FLOAT specifies the data type approximate numeric, with binary 
     298    precision equal to or greater than the value of the specified 
     299    <precision>. The maximum value of <precision> is implementation- 
     300    defined. <precision> shall not be greater than this value." 
     301    """ 
    293302    default_pytype = float 
    294303 
    295304 
    296 # "REAL specifies the data type approximate numeric, with implementation- 
    297 #    defined precision." 
    298 # 
    299 # "DOUBLE PRECISION specifies the data type approximate numeric, 
    300 #    with implementation-defined precision that is greater than the 
    301 #    implementation-defined precision of REAL. 
    302  
    303305class SQL92REAL(FrozenPrecisionType): 
    304     """Base class for SQL 92 REAL types.""" 
     306    """Base class for SQL 92 REAL types. 
     307     
     308    "REAL specifies the data type approximate numeric, with implementation- 
     309    defined precision." 
     310    """ 
    305311    default_pytype = float 
    306312    # By "precision" here, we mean the number of binary digits 
     
    322328 
    323329class SQL92DOUBLE(SQL92REAL): 
    324     """Base class for SQL 92 DOUBLE PRECISION types.""" 
     330    """Base class for SQL 92 DOUBLE PRECISION types. 
     331     
     332    "DOUBLE PRECISION specifies the data type approximate numeric, 
     333    with implementation-defined precision that is greater than the 
     334    implementation-defined precision of REAL." 
     335    """ 
    325336    _precision = max_precision = 53 
    326337    default_adapters = {float: adapters.float_to_SQL92DOUBLE()} 
     
    334345 
    335346 
    336 # "NUMERIC specifies the data type exact numeric, with the decimal 
    337 # precision and scale specified by the <precision> and <scale>." 
    338 # 
    339 # "DECIMAL specifies the data type exact numeric, with the decimal 
    340 # scale specified by the <scale> and the implementation-defined 
    341 # decimal precision equal to or greater than the value of the 
    342 # specified <precision>." 
    343 # 
    344 # In terms of adaptation, there's not much difference between these. 
    345 # Plenty of databases make them synonyms anyway. 
    346  
    347347class SQL92DECIMAL(AdjustablePrecisionType): 
    348      
     348    """Base class for SQL 92 DECIMAL types. 
     349     
     350    For exact numeric types, 'precision' and 'scale' refer to decimal digits. 
     351     
     352    SQL 92 says: 
     353        "NUMERIC specifies the data type exact numeric, with the decimal 
     354        precision and scale specified by the <precision> and <scale>." 
     355         
     356        "DECIMAL specifies the data type exact numeric, with the decimal 
     357        scale specified by the <scale> and the implementation-defined 
     358        decimal precision equal to or greater than the value of the 
     359        specified <precision>." 
     360     
     361    In terms of adaptation, there's not much difference between these. 
     362    Plenty of databases make them synonyms anyway. 
     363    """ 
    349364    scale = None 
    350365     
    351     # For exact numeric types, 'precision' refers to decimal digits 
    352366    _precision = 18 
    353367    max_precision = 1000 
     
    423437 
    424438class SQL99BOOLEAN(DatabaseType): 
    425     """DatabaseType which uses boolean values (True, False, Null).""" 
    426     # ANSI SQL:1999 says something like: 
    427     # "The data type boolean comprises the distinct truth values 
    428     # true and false. Unless prohibited by a NOT NULL constraint, 
    429     # the boolean data type also supports the unknown truth value as 
    430     # the null value. This specification does not make a distinction 
    431     # between the null value of the boolean data type and the unknown 
    432     # truth value that is the result of an SQL <predicate>, <search 
    433     # condition>, or <boolean value expression>; they may be used 
    434     # interchangeably to mean exactly the same thing." 
     439    """DatabaseType which uses boolean values (True, False, Null). 
     440     
     441    ANSI SQL:1999 says something like: 
     442    "The data type boolean comprises the distinct truth values 
     443    true and false. Unless prohibited by a NOT NULL constraint, 
     444    the boolean data type also supports the unknown truth value as 
     445    the null value. This specification does not make a distinction 
     446    between the null value of the boolean data type and the unknown 
     447    truth value that is the result of an SQL <predicate>, <search 
     448    condition>, or <boolean value expression>; they may be used 
     449    interchangeably to mean exactly the same thing." 
     450    """ 
    435451    default_pytype = bool 
    436452    default_adapters = {bool: adapters.bool_to_SQL99BOOLEAN()} 
     
    530546            if precision <= dbtype.max_precision: 
    531547                return dbtype(precision=precision) 
    532         return self.decimal_type(precision=precision
     548        return self.decimal_type(precision=precision, scale=precision
    533549     
    534550    def dbtype_for_str(self, hints): 
     
    551567        return self.known_types['bool'][0]() 
    552568     
     569    # These are not adapter.push(bool) (which are used on one side of  
     570    # a comparison). Instead, these are used when the whole (sub)expression 
     571    # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 
     572    expr_true = "TRUE" 
     573    expr_false = "FALSE" 
     574     
     575    # Because True and False get used so much in deparsing, 
     576    # we include a memoized function for obtaining SQLExpressions. 
     577    _adapted_bools = None 
     578    def bool_exprs(self, exprclass): 
     579        """Return SQLExpressions for expr_true, expr_false, comp_true and comp_false. 
     580         
     581        expr_true and expr_false are used when a (sub)expression is True or 
     582        False; for example, "WHERE TRUE and a.b = 3". 
     583         
     584        comp_true and comp_false are used in comparisons; for example, 
     585        "WHERE x.y == TRUE". 
     586         
     587        In many databases, these are equivalent. Microsoft SQL Server is a 
     588        notable exception, requiring "(1=1)" for expr_true, but "TRUE" for 
     589        comp_true. 
     590        """ 
     591        t = self.database_type(bool) 
     592        adapter = t.default_adapter(bool) 
     593         
     594        expr_true = exprclass(self.expr_true, '', t, bool) 
     595        expr_true.adapter = adapter 
     596        expr_true.value = True 
     597         
     598        expr_false = exprclass(self.expr_false, '', t, bool) 
     599        expr_false.adapter = adapter 
     600        expr_false.value = False 
     601         
     602        if self._adapted_bools is None: 
     603            comp_true = adapter.push(True, t) 
     604            comp_false = adapter.push(False, t) 
     605            self._adapted_bools = (comp_true, comp_false) 
     606        else: 
     607            comp_true, comp_false = self._adapted_bools 
     608         
     609        comp_true = exprclass(comp_true, '', t, bool) 
     610        comp_true.adapter = adapter 
     611        comp_true.value = True 
     612         
     613        comp_false = exprclass(comp_false, '', t, bool) 
     614        comp_false.adapter = adapter 
     615        comp_false.value = False 
     616         
     617        return (expr_true, expr_false, comp_true, comp_false) 
     618     
    553619    def dbtype_for_datetime_datetime(self, hints): 
    554620        return self.known_types['datetime'][0]() 
     
    564630            # If your DB has an INTERVAL datatype, you should provide a 
    565631            # native INTERVAL type. You'll also have to update the date 
    566             # arithmetic inside the decompiler and add a timedelta adapter. 
     632            # arithmetic inside the deparser and add a timedelta adapter. 
    567633            return self.known_types['timedelta'][0]() 
    568634        except (KeyError, IndexError): 
  • trunk/geniusql/logic.py

    r134 r135  
    184184 
    185185 
    186 from geniusql import codewalk 
     186from geniusql import codewalk, astwalk 
    187187 
    188188 
     
    244244 
    245245class Expression(object): 
    246     """A filter for objects.""" 
     246    """A filter for objects (an AST).""" 
    247247     
    248248    def __init__(self, func=None, kwtypes=None, earlybind=True): 
     
    260260        """ 
    261261        if func is None: 
    262             func = lambda x: True 
    263             self._load_func(func, False) 
     262            self.func = lambda *args, **kw: True 
     263            self.ast = astwalk.AST(ast.Const(True)) 
     264            self.ast.star_args = "args" 
     265            self.ast.dstar_args = "kwargs" 
    264266        else: 
    265             self._load_func(func, earlybind) 
     267            self.func = func 
     268            lp = astwalk.LambdaParser(func, env=builtins, reduce=earlybind) 
     269            lp.walk() 
     270            self.ast = lp.ast 
     271         
     272        # Update func_globals so self.evaluate() works. 
     273        func.func_globals.update(builtins) 
    266274         
    267275        if kwtypes is None: 
     
    270278        self.kwargs = {} 
    271279     
    272     def _load_func(self, func, earlybind=True): 
    273         # I can't believe this actually works (knock on wood). 
    274         func.func_globals.update(builtins) 
    275         if earlybind: 
    276             # Early-bind as much as possible. 
    277             binder = codewalk.EarlyBinder(func) 
    278             self.func = binder.function() 
    279         else: 
    280             self.func = func 
    281      
    282280    def code(self): 
    283281        """Return source code for self.func.""" 
    284282        if hasattr(self, 'func'): 
    285             decom = codewalk.LambdaDecompiler(self.func, env=builtins) 
     283            decom = astwalk.LambdaDeparser(self.ast, env=builtins) 
    286284            return decom.code() 
    287285        else: 
     
    336334        else: 
    337335            func, self.kwtypes, self.kwargs = state 
     336         
    338337        # The most difficult thing about Expressions is unpickling. 
    339338        # Any func_globals at the time of pickling are lost, so any 
     
    341340        # such objects need to be injected into logic.builtins 
    342341        # if you want them to be available here. 
    343         f = eval(func, builtins) 
    344         self._load_func(f) 
     342        self.func = eval(func, builtins) 
     343        lp = astwalk.LambdaParser(self.func, env=builtins, reduce=True) 
     344        lp.walk() 
     345        self.ast = lp.ast 
    345346     
    346347    def is_constant(self, value): 
     
    358359        e = logic.Expression(lambda x: x.a == 3 and x.b == 1) 
    359360    """ 
    360     co, names, consts = [], ['x', ], [None, ] 
    361     i = 0 
    362     for key, val in kwargs.iteritems(): 
    363         i += 1 
    364         names.append(key) 
    365         consts.append(val) 
     361    items = kwargs.items() 
     362    co = [] 
     363    kwlen = len(kwargs) 
     364    jump_target = (16 * kwlen) - 7 
     365    for i in xrange(1, kwlen + 1): 
    366366        co += [124, 0, 0, 
    367367               105, i, 0, 
    368368               100, i, 0, 
    369369               106, 2, 0, 
    370                111, 0, 0, 
     370               # Point all jump (111) instructions at len(co) - 4 
     371               111, jump_target - (((i - 1) * 16) + 12), 0, 
    371372               1, 
    372373               ] 
     
    376377    co.append(83) 
    377378     
    378     # Figure JUMP targets 
    379     for op in range(len(co)): 
    380         if co[op] == 111: 
    381             co[op + 1] = (len(co) - 4) - op 
     379    items = [('x', None)] + items 
     380    names, consts = zip(*items) 
    382381     
    383382    # Form code object and function. 
     
    386385    #      filename, name, firstlineno, lnotab[, freevars[, cellvars]]) 
    387386    co = CodeType(1, 1, 2, 67, ''.join(map(chr, co)), 
    388                   tuple(consts), tuple(names), ('x', ), 
    389                   '', '<lambda>', 1, '') 
     387                  consts, names, ('x', ), '', '<lambda>', 1, '') 
    390388    func = FunctionType(co, {}) 
    391389    return Expression(func, earlybind=False) 
  • trunk/geniusql/logicfuncs.py

    r134 r135  
    6464    """Late-bound datetime.datetime.now(). Taint this when early binding.""" 
    6565    return _datetime.datetime.now() 
    66 now.bind_late = True 
     66now.irreducible = True 
    6767 
    6868def utcnow(): 
    6969    """Late-bound datetime.datetime.utcnow(). Taint this when early binding.""" 
    7070    return _datetime.datetime.utcnow() 
    71 utcnow.bind_late = True 
     71utcnow.irreducible = True 
    7272 
    7373def today(): 
    7474    """Late-bound datetime.date.today(). Taint this when early binding.""" 
    7575    return _datetime.date.today() 
    76 today.bind_late = True 
     76today.irreducible = True 
    7777 
    7878def iscurrentweek(value): 
     
    8282    else: 
    8383        return False 
    84 iscurrentweek.bind_late = True 
     84iscurrentweek.irreducible = True 
    8585 
    8686def count(values): 
  • trunk/geniusql/objects.py

    r134 r135  
    2121from geniusql import dbtypes 
    2222from geniusql import conns 
    23 from geniusql import decompil
     23from geniusql import depars
    2424from geniusql import isolation 
    2525from geniusql import logic 
     
    331331        tpair = [(self.qname, self)] 
    332332        restriction = logic.combine(restriction, kwargs) 
    333         decom = self.schema.db.decompiler(tpair, restriction, 
    334                                           self.schema.db.typeset) 
    335 ##        decom.verbose = True 
    336         code = decom.code() 
    337         if decom.imperfect: 
     333        dep = self.schema.db.deparser(tpair, restriction, 
     334                                      self.schema.db.typeset) 
     335##        dep.verbose = True 
     336        code = dep.code() 
     337        if dep.imperfect: 
    338338            raise ValueError("The given restriction could not safely be " 
    339339                             "translated to SQL.", restriction, code) 
     
    406406         
    407407        if parms: 
     408            w = self.id_clause(**kwargs) 
    408409            sql = ('UPDATE %s SET %s WHERE %s;' % 
    409                    (self.qname, ", ".join(parms), self.id_clause(**kwargs))) 
     410                   (self.qname, ", ".join(parms), w)) 
    410411            self.schema.db.execute(sql) 
    411412     
     
    423424         
    424425        if parms: 
     426            w = self.whereclause(restriction, **kwargs) 
    425427            sql = ('UPDATE %s SET %s WHERE %s;' % 
    426                    (self.qname, ", ".join(parms), 
    427                     self.whereclause(restriction, **kwargs))) 
     428                   (self.qname, ", ".join(parms), w)) 
    428429            self.schema.db.execute(sql) 
    429430     
    430431    def delete(self, **kwargs): 
    431432        """Delete all rows which match the given identifier kwargs.""" 
    432         self.schema.db.execute('DELETE FROM %s WHERE %s;' % 
    433                                (self.qname, self.id_clause(**kwargs))) 
     433        w = self.id_clause(**kwargs) 
     434        sql = 'DELETE FROM %s WHERE %s;' % (self.qname, w) 
     435        self.schema.db.execute(sql) 
    434436     
    435437    def delete_all(self, restriction=None, **kwargs): 
    436438        """Delete all rows which match the given restriction.""" 
    437439        w = self.whereclause(restriction, **kwargs) 
    438         self.schema.db.execute('DELETE FROM %s WHERE %s;' % (self.qname, w)) 
     440        sql = 'DELETE FROM %s WHERE %s;' % (self.qname, w) 
     441        self.schema.db.execute(sql) 
    439442     
    440443    def select(self, restriction=None, **kwargs): 
     
    456459        dataset = self.schema.db.select((self, attrs, restriction, order), 
    457460                                        limit=limit, strict=False) 
    458         if restriction and dataset.selector.imperfect: 
     461        if dataset.selector.imperfect: 
     462            if restriction is None: 
     463                raise ValueError("Could not generate perfect SQL.") 
    459464            # Run a dummy object through our restriction before yielding. 
    460465            for row in dataset: 
     
    770775     
    771776    typeset = dbtypes.DatabaseTypeSet() 
    772     decompiler = decompile.SQLDecompiler 
     777    deparser = deparse.SQLDeparser 
    773778    joinwrapper = select.TableWrapper 
    774779    selectwriter = select.SelectWriter 
     
    910915         
    911916        sel = self.selectwriter(self, query) 
    912         data, _ = self.fetch(sel.sql(distinct, limit)) 
     917        sql = sel.sql(distinct, limit) 
     918        data, _ = self.fetch(sql) 
    913919         
    914920        d = Dataset(sel, data) 
     
    931937         
    932938        sel = self.selectwriter(self, query) 
    933         newtable = sel.result 
    934          
    935         # The selectwriter doesn't give the result table a name. 
    936         newtable.name = newtable.schema.table_name(name) 
    937         newtable.qname = self.quote(newtable.name) 
    938          
     939        newtable = sel.result_table(name) 
    939940        # CREATE TABLE 
    940941        newtable.schema[name] = newtable 
     
    950951            data, _ = self.fetch(selsql) 
    951952             
     953            output_keys = [names[0] for names in sel.output] 
    952954            for row in Dataset(sel, data): 
    953                 row = dict(zip(sel.output_keys, row)) 
     955                row = dict(zip(output_keys, row)) 
    954956                # Run a dummy object through our restriction before inserting. 
    955957                if not query.restriction(_ImperfectDummy(**row)): 
     
    958960                newtable.insert(**row) 
    959961        else: 
     962            qnames = [names[2] for names in sel.output] 
    960963            sql = ("INSERT INTO %s (%s) %s" % 
    961                    (newtable.qname, ", ".join(sel.output_list), selsql)) 
     964                   (newtable.qname, ", ".join(qnames), selsql)) 
    962965            self.execute(sql) 
    963966         
     
    993996        self.dataset = dataset 
    994997        self.dataiter = iter(dataset.data) 
     998         
     999        # pre-fetch cols and cache in an optimal format. 
     1000        c = dataset.selector.output_cols 
     1001        self.cols = [c[coldata[0]] for coldata in dataset.selector.output] 
    9951002     
    9961003    def __iter__(self): 
     
    10061013        under the assumption that the caller will do so itself. 
    10071014        """ 
    1008         d = self.dataset 
    1009          
    10101015        raw_row = self.dataiter.next() 
    1011          
    1012         row = [] 
    1013         for i, colkey in enumerate(d.selector.output_keys): 
    1014             val = raw_row[i] 
    1015             col = d.selector.result[colkey] 
    1016             # Any column value can be None. Don't coerce it. 
    1017             if val is not None: 
    1018                 val = col.adapter.pull(val, col.dbtype) 
    1019             row.append(val) 
    1020          
    1021         return row 
    1022  
     1016        return [col.adapter.pull(val, col.dbtype) for val, col 
     1017                in zip(raw_row, self.cols)] 
     1018 
  • trunk/geniusql/providers/ado.py

    r134 r135  
    3131 
    3232import geniusql 
    33 from geniusql import adapters, conns, decompile, errors, select 
     33from geniusql import adapters, conns, deparse, errors, select 
    3434 
    3535adOpenForwardOnly = 0 
     
    7373     
    7474    def pull(self, value, dbtype): 
     75        if value is None: 
     76            return None 
    7577        if isinstance(value, unicode): 
    7678            # The value is a stringified NUMERIC of seconds. 
     
    115117     
    116118    def pull(self, value, dbtype): 
     119        if value is None: 
     120            return None 
    117121        t = timedelta_from_com(value, self.epoch) 
    118122        if t.days: 
     
    129133    def pull(self, value, dbtype): 
    130134        """Return a valid datetime.datetime from a COM date/time object.""" 
     135        if value is None: 
     136            return None 
    131137        return datetime.datetime(value.year, value.month, value.day, 
    132138                                 value.hour, value.minute, value.second, 
     
    167173     
    168174    def pull(self, value, dbtype): 
     175        if value is None: 
     176            return None 
    169177        return datetime.date(value.year, value.month, value.day) 
    170178     
     
    202210 
    203211 
    204 class ADOSQLDecompiler(decompile.SQLDecompiler): 
     212class ADOSQLDeparser(deparse.SQLDeparser): 
    205213     
    206214    # --------------------------- Dispatchees --------------------------- # 
     
    216224    def containedby(self, op1, op2): 
    217225        self.imperfect = True 
    218         return decompile.SQLDecompiler.containedby(self, op1, op2) 
     226        return deparse.SQLDeparser.containedby(self, op1, op2) 
    219227     
    220228    def builtins_icontainedby(self, op1, op2): 
     
    455463class ADODatabase(geniusql.Database): 
    456464     
    457     decompiler = ADOSQLDecompiler 
     465    deparser = ADOSQLDeparser 
    458466    selectwriter = ADOSelectWriter 
    459467     
  • trunk/geniusql/providers/firebird.py

    r134 r135  
    99 
    1010import geniusql 
    11 from geniusql import adapters, conns, dbtypes, decompile, errors, select, typerefs 
     11from geniusql import adapters, conns, dbtypes, deparse, errors, select, typerefs 
    1212from geniusql import isolation as _isolation 
    1313 
     
    4545     
    4646    def pull(self, value, dbtype): 
     47        if value is None: 
     48            return None 
    4749        return self.pytype(value) 
    4850 
     
    9597     
    9698    def pull(self, value, dbtype): 
     99        if value is None: 
     100            return None 
    97101        # TIMESTAMP - TIMESTAMP => DECIMAL(18, 9) Days + Fraction of day 
    98102        # DATE - DATE =>           DECIMAL(9, 0) representing # of Days 
     
    140144class Firebird_decimal_adapter(adapters.decimal_to_SQL92DECIMAL): 
    141145    def pull(self, value, dbtype): 
     146        if value is None: 
     147            return None 
    142148        if isinstance(value, tuple): 
    143149            return normalize(value, typerefs.decimal)[0] 
     
    146152class Firebird_decimal_Decimal_adapter(adapters.decimal_to_SQL92DECIMAL): 
    147153    def pull(self, value, dbtype): 
     154        if value is None: 
     155            return None 
    148156        if isinstance(value, tuple): 
    149157            return normalize(value, typerefs.decimal.Decimal)[0] 
     
    152160class Firebird_fixedpoint_adapter(adapters.fixedpoint_to_SQL92DECIMAL): 
    153161    def pull(self, value, dbtype): 
     162        if value is None: 
     163            return None 
    154164        fp = typerefs.fixedpoint.FixedPoint 
    155165        if isinstance(value, tuple): 
     
    172182class Firebird_int_adapter(adapters.int_to_SQL92INTEGER): 
    173183    def pull(self, value, dbtype): 
     184        if value is None: 
     185            return None 
    174186        if isinstance(value, tuple): 
    175187            return normalize(value, int)[0] 
     
    178190class Firebird_long_adapter(adapters.int_to_SQL92INTEGER): 
    179191    def pull(self, value, dbtype): 
     192        if value is None: 
     193            return None 
    180194        if isinstance(value, tuple): 
    181195            return normalize(value, long)[0] 
     
    184198class Firebird_number_to_SQL92DECIMAL(adapters.number_to_SQL92DECIMAL): 
    185199    def pull(self, value, dbtype): 
     200        if value is None: 
     201            return None 
    186202        if isinstance(value, tuple): 
    187203            return normalize(value, self.pytype)[0] 
     
    389405class FirebirdTypeSet(dbtypes.DatabaseTypeSet): 
    390406     
     407    # Firebird doesn't have true or false keywords. 
     408    expr_true = "1=1" 
     409    expr_false = "1=0" 
     410     
    391411    known_types = {'float': [FLOAT, DOUBLE], 
    392412                   'varchar': [VARCHAR, BLOB], 
     
    416436 
    417437 
    418 class FirebirdSQLDecompiler(decompile.SQLDecompiler): 
    419      
    420     # Firebird doesn't have true or false keywords. 
    421     bool_true = "1=1" 
    422     bool_false = "1=0" 
     438class FirebirdSQLDeparser(deparse.SQLDeparser): 
    423439     
    424440    like_escapes = [("\\", r"\\"), ("%", r"\%"), ("_", r"\_")] 
     
    892908     
    893909    selectwriter = FirebirdSelectWriter 
    894     decompiler = FirebirdSQLDecompiler 
     910    deparser = FirebirdSQLDeparser 
    895911     
    896912    typeset = FirebirdTypeSet() 
  • trunk/geniusql/providers/msaccess.py

    r134 r135  
    9494     
    9595    def pull(self, value, dbtype): 
     96        if value is None: 
     97            return None 
    9698        if isinstance(value, tuple): 
    9799            # See http://groups.google.com/group/comp.lang.python/ 
     
    104106     
    105107    def pull(self, value, dbtype): 
     108        if value is None: 
     109            return None 
    106110        # pywin32 build 205 began support for returning 
    107111        # COM Currency objects as decimal objects. 
     
    118122     
    119123    def pull(self, value, dbtype): 
     124        if value is None: 
     125            return None 
    120126        if isinstance(value, typerefs.decimal.Decimal): 
    121127            value = str(value) 
     
    142148    """ 
    143149    # ADO comparison operators for strings are case-insensitive. 
    144     if op < 6: 
    145         # ('<', '<=', '==', '!=', '>', '>=') 
     150    if op in ('<', '<=', '==', '!=', '>', '>='): 
    146151        # Some operations on strings can be emulated with the 
    147152        # StrComp function. Oddly enough, "StrComp(x, y) op 0" 
     
    341346 
    342347 
    343 class MSAccessDecompiler(ado.ADOSQLDecompiler): 
    344     sql_cmp_op = ('<', '<=', '=', '<>', '>', '>=', 'in', 'not in') 
     348class MSAccessDeparser(ado.ADOSQLDeparser): 
     349    sql_cmp_op = {'<': '<', 
     350                  '<=': '<=', 
     351                  '==': '=', 
     352                  '!=': '<>', 
     353                  '>': '>', 
     354                  '>=': '>=', 
     355                  'in': 'in', 
     356                  'not in': 'not in', 
     357                  } 
    345358     
    346359    like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"), 
     
    645658class MSAccessDatabase(ado.ADODatabase): 
    646659     
    647     decompiler = MSAccessDecompiler 
     660    deparser = MSAccessDeparser 
    648661    typeset = MSAccessTypeSet() 
    649662    connectionmanager = MSAccessConnectionManager 
  • trunk/geniusql/providers/mysql.py

    r134 r135  
    1717 
    1818import geniusql 
    19 from geniusql import adapters, dbtypes, conns, decompile, errors, providers, typerefs 
     19from geniusql import adapters, dbtypes, conns, deparse, errors, providers, typerefs 
    2020 
    2121 
     
    3939        if op2.dbtype in (FLOAT, DOUBLE): 
    4040            # MySQL provides no reliable method to compare floats in SQL. 
    41             # Raising TypeError will tell the SQL decompiler to mark float 
     41            # Raising TypeError will tell the SQL deparser to mark float 
    4242            # comparisons as imperfect (so they'll be done in Python). 
    4343            raise TypeError("MySQL cannot reliably compare floats: %s" % sql) 
     
    242242class MySQLTypeSet(dbtypes.DatabaseTypeSet): 
    243243     
     244    # TRUE and FALSE only work with 4.1 or better. 
     245    expr_true = "1" 
     246    expr_false = "0" 
     247     
    244248    known_types = {'float': [FLOAT, DOUBLE], 
    245249                   # MySQL VARBINARY/BLOBs will do case-sensitive comparisons. 
     
    259263    def __init__(self, version): 
    260264        self.version = version 
     265         
     266        if self.version >= providers.Version("4.1.1"): 
     267            # TRUE and FALSE only work with 4.1 or better. 
     268            self.expr_true = "TRUE" 
     269            self.expr_false = "FALSE" 
    261270        if self.version >= providers.Version("5.0.3"): 
    262271            self.known_types['numeric'] = [DECIMAL503] 
     
    267276 
    268277 
    269 class MySQLDecompiler(decompile.SQLDecompiler): 
    270      
    271     # TRUE and FALSE only work with 4.1 or better. 
    272     bool_true = "1" 
    273     bool_false = "0" 
     278class MySQLDeparser(deparse.SQLDeparser): 
    274279     
    275280    like_escapes = [("%", r"\%"), ("_", r"\_")] 
     
    279284 
    280285 
    281 class MySQLDecompiler411(MySQLDecompiler): 
    282      
    283     # TRUE and FALSE only work with 4.1 or better. 
    284     bool_true = "TRUE" 
    285     bool_false = "FALSE" 
     286class MySQLDeparser411(MySQLDeparser): 
    286287     
    287288    # Before MySQL 4.1.1, BINARY comparisons could use UPPER() 
     
    583584                                          if k in connargs]) 
    584585         
    585         self.decompiler = MySQLDecompiler 
     586        self.deparser = MySQLDeparser 
    586587         
    587588        # Get the version string from MySQL, to see if we need 
    588         # a different decompiler. 
     589        # a different deparser. 
    589590        conn = self.connections._get_conn(master=True) 
    590591        rowdata, cols = self.fetch("SELECT version();", conn) 
     
    593594        self._version = providers.Version(v) 
    594595         
    595         # decompiler 
     596        # deparser 
    596597        if self._version > providers.Version("4.1.1"): 
    597             self.decompiler = MySQLDecompiler411 
     598            self.deparser = MySQLDeparser411 
    598599         
    599600        self.typeset = MySQLTypeSet(self._version) 
  • trunk/geniusql/providers/postgres.py

    r134 r135  
    1313 
    1414import geniusql 
    15 from geniusql import adapters, dbtypes, decompile, errors 
     15from geniusql import adapters, dbtypes, deparse, errors 
    1616 
    1717 
     
    4444     
    4545    def pull(self, value, dbtype): 
     46        if value is None: 
     47            return None 
    4648        if isinstance(value, datetime.timedelta): 
    4749            return value 
     
    121123     
    122124    def pull(self, value, dbtype): 
     125        if value is None: 
     126            return None 
    123127        # Unescape octal sequences 
    124128        value = unescape_oct.sub(replace_unoct, value) 
    125         if isinstance(value, unicode): 
    126             return value.encode(dbtype.encoding) 
    127         else: 
    128             return str(value) 
     129         
     130        # Return values don't ever seem to be unicode 
     131##        if isinstance(value, unicode): 
     132##            return value.encode(dbtype.encoding) 
     133##        else: 
     134        return value 
    129135 
    130136 
     
    144150     
    145151    def pull(self, value, dbtype): 
     152        if value is None: 
     153            return None 
    146154        # Unescape octal sequences 
    147155        value = unescape_oct.sub(replace_unoct, value) 
    148         if isinstance(value, unicode): 
    149             return value 
    150         else: 
    151             return unicode(value, dbtype.encoding) 
     156        # Return values don't ever seem to be unicode 
     157##        if isinstance(value, unicode): 
     158##            return value 
     159##        else: 
     160        return unicode(value, dbtype.encoding) 
    152161 
    153162 
     
    169178     
    170179    def pull(self, value, dbtype): 
     180        if value is None: 
     181            return None 
    171182        # Unescape octal sequences 
    172183        value = unescape_oct.sub(replace_unoct, value) 
    173184         
    174         # Coerce to str for pickle.loads restriction. 
    175         if isinstance(value, unicode): 
    176             value = value.encode(dbtype.encoding) 
    177         else: 
    178             value = str(value) 
     185        # Return values don't ever seem to be unicode. 
     186##        # Coerce to str for pickle.loads restriction. 
     187##        if isinstance(value, unicode): 
     188##            value = value.encode(dbtype.encoding) 
     189##        else: 
     190##            value = str(value) 
    179191        return pickle.loads(value) 
    180192 
     
    377389 
    378390 
    379 class PgDecompiler(decompile.SQLDecompiler): 
     391class PgDeparser(deparse.SQLDeparser): 
    380392     
    381393    like_escapes = [("%", r"\\%"), ("_", r"\\_")] 
     
    665677    encoding = 'SQL_ASCII' 
    666678     
    667     decompiler = PgDecompiler 
     679    deparser = PgDeparser 
    668680    schemaclass = PgSchema 
    669681    typeset = PgTypeSet() 
  • trunk/geniusql/providers/psycopg.py

    r134 r135  
    8484        cursor = conn.cursor() 
    8585        try: 
    86             cursor.execute(sql) 
     86            try: 
     87                cursor.execute(sql) 
     88            except _psycopg.ProgrammingError, x: 
     89                x.args += (sql,) 
     90                raise 
    8791        finally: 
    8892            cursor.close() 
     
    104108        cursor = conn.cursor() 
    105109        try: 
    106             cursor.execute(sql) 
     110            try: 
     111                cursor.execute(sql) 
     112            except _psycopg.ProgrammingError, x: 
     113                x.args += (sql,) 
     114                raise 
    107115            data = cursor.fetchall() 
    108116            coldefs = cursor.description 
  • trunk/geniusql/providers/sqlite.py

    r134 r135  
    55 
    66import geniusql 
    7 from geniusql import adapters, dbtypes, conns, decompile, errors, providers, select, typerefs 
     7from geniusql import adapters, dbtypes, conns, deparse, errors, providers, select, typerefs 
    88from geniusql import isolation as _isolation 
    99 
     
    3838_autoincrement_support = (_version >= providers.Version([3, 1, 0])) 
    3939_cast_support = (_version >= providers.Version([3, 2, 3])) 
    40  
     40_trim_support = (_version >= providers.Version([3, 3, 14])) 
    4141 
    4242# ------------------------------ Adapters ------------------------------ # 
     
    122122 
    123123 
    124  
    125124# --------------------------- Database Types --------------------------- # 
    126125 
     
    143142                             None: SQLite_Pickler(), 
    144143                             }) 
     144 
    145145 
    146146class REAL(dbtypes.SQL92DOUBLE): 
     
    199199        'other': [], 
    200200        } 
    201  
    202  
    203  
    204 class SQLiteDecompiler(decompile.SQLDecompiler): 
    205      
    206     bool_true = "1" 
    207     bool_false = "0" 
     201     
     202    # These are not adapter.push(bool) (which are used on one side of  
     203    # a comparison). Instead, these are used when the whole (sub)expression 
     204    # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 
     205    expr_true = "1" 
     206    expr_false = "0" 
     207 
     208 
     209 
     210class SQLiteDeparser(deparse.SQLDeparser): 
    208211     
    209212    like_escapes = [("%", "\%"), ("_", "\_")] 
     
    760763        """ 
    761764        if column.autoincrement: 
     765            # From http://www.sqlite.org/datatypes.html: 
     766            # "One exception to the typelessness of SQLite is a column whose 
     767            # type is INTEGER PRIMARY KEY. (And you must use "INTEGER" not 
     768            # "INT". A column of type INT PRIMARY KEY is typeless just like 
     769            # any other.) INTEGER PRIMARY KEY columns must contain a 32-bit 
     770            # signed integer. Any attempt to insert non-integer data will 
     771            # result in an error." 
    762772            coldef = "INTEGER PRIMARY KEY AUTOINCREMENT" 
    763773        else: 
     
    839849    sql_name_max_length = 0 
    840850     
    841     decompiler = SQLiteDecompiler 
     851    deparser = SQLiteDeparser 
    842852    selectwriter = SQLiteSelectWriter 
    843853    typeset = SQLiteTypeSet() 
     
    894904                except (_sqlite.OperationalError, _sqlite.DatabaseError), x: 
    895905                    msg = x.args[0] 
    896                     if ((msg.startswith("no such") or 
     906                    if ((msg.startswith("no such table") or 
    897907                         msg == "database schema has changed")): 
    898908                        if not self.connections.in_transaction(): 
  • trunk/geniusql/providers/sqlserver.py

    r134 r135  
    2222    """ 
    2323    # ADO comparison operators for strings are case-insensitive. 
    24     if op < 6: 
    25         # ('<', '<=', '==', '!=', '>', '>=') 
     24    if op in ('<', '<=', '==', '!=', '>', '>='): 
    2625        # Some operations on strings can be emulated with the 
    2726        # Convert function. 
     
    236235 
    237236class SQLServerTypeSet(dbtypes.DatabaseTypeSet): 
     237     
     238    # These are not adapter.push(bool) (which are used on one side of  
     239    # a comparison). Instead, these are used when the whole (sub)expression 
     240    # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 
     241    expr_true = "(1=1)" 
     242    expr_false = "(1=0)" 
    238243     
    239244    known_types = {'float': [REAL, FLOAT], 
     
    279284 
    280285 
    281 class SQLServerDecompiler(ado.ADOSQLDecompiler): 
    282      
    283     # These are not the same as coerce_bool_to_any (which is used on one side of  
    284     # a comparison). Instead, these are used when the whole (sub)expression 
    285     # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 
    286     bool_true = "(1=1)" 
    287     bool_false = "(1=0)" 
     286class SQLServerDeparser(ado.ADOSQLDeparser): 
    288287     
    289288    like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"), 
     
    481480class SQLServerDatabase(ado.ADODatabase): 
    482481     
    483     decompiler = SQLServerDecompiler 
     482    deparser = SQLServerDeparser 
    484483    typeset = SQLServerTypeSet() 
    485484    connectionmanager = SQLServerConnectionManager 
  • trunk/geniusql/select.py

    r134 r135  
    193193    input_list: a list of SQL expressions, one for each column in the 
    194194        SELECT clause. These will include any "expr AS name" alias. 
    195     output_list: a list of the SQL names (aliases), one per output column. 
    196     output_keys: the keys for each column in the final table. 
     195    output: a list of tuples of the form: 
     196        (column key, column, SQL name (alias), quoted SQL name (alias)) 
     197        One per output column. 
     198    output_cols: a dict of source cols for the final table. 
    197199    """ 
    198200     
     
    209211        if isinstance(relation, Join): 
    210212            if isinstance(relation.table1, Join): 
    211                 self.result = relation.table2.schema.table('') 
    212             else: 
    213                 self.result = relation.table1.schema.table('') 
     213                self.schema = relation.table2.schema 
     214            else: 
     215                self.schema = relation.table1.schema 
    214216            self.tables = self.wrap(relation) 
    215217            self.fromclause = self.joinclause(self.tables) 
    216218        elif isinstance(relation, geniusql.Schema): 
    217219            # This is how we say we want to SELECT scalars (no FROM clause) 
    218             self.result = relation.table('') 
     220            self.schema = relation 
    219221            self.tables = [] 
    220222            self.fromclause = "" 
    221223        else: 
    222             self.result = relation.schema.table('') 
     224            self.schema = relation.schema 
    223225            self.tables = [self.db.joinwrapper(relation)] 
    224226            self.fromclause = relation.qname 
     
    227229         
    228230        self.input_list = [] 
    229         self.output_list = [] 
    230         self.output_keys = [] 
     231        self.output = [] 
     232        self.output_cols = {} 
    231233        self._groupby = [] 
    232234        if isinstance(query.attributes, logic.Expression): 
    233             self.decompile_attributes() 
     235            self.deparse_attributes() 
    234236        else: 
    235237            if isinstance(relation, Join): 
     
    238240                    alias = t.alias or t.qname 
    239241                    table = t.table 
    240                     for a in attrs: 
    241                         self._copy_column(table, alias, a) 
     242                    for colkey in attrs: 
     243                        col = table[colkey] 
     244                        if colkey in self.output_cols: 
     245                            # Get the key for the table. 
     246                            colkey = '%s_%s' % (table.schema.key_for(table), colkey) 
     247                            colname = '%s_%s' % (table.name, col.name) 
     248                            colqname = self.db.quote(colname) 
     249                            selname = '%s.%s AS %s' % (alias, col.qname, colqname) 
     250                        else: 
     251                            colname = col.name 
     252                            colqname = col.qname 
     253                            selname = '%s.%s' % (alias, colqname) 
     254                        self.input_list.append(selname) 
     255                        self.output.append((colkey, colname, colqname)) 
     256                        self.output_cols[colkey] = col 
    242257            else: 
    243258                # 'relation' is a single Table object. 
    244                 for a in query.attributes: 
    245                     self._copy_column(relation, None, a) 
     259                for colkey in query.attributes: 
     260                    col = relation[colkey] 
     261                    self.input_list.append(col.qname) 
     262                    self.output.append((colkey, col.name, col.qname)) 
     263                    self.output_cols[colkey] = col 
    246264         
    247265        if query.order is None: 
    248266            self.order = None 
    249267        elif isinstance(query.order, logic.Expression): 
    250             self.decompile_order() 
     268            self.deparse_order() 
    251269        else: 
    252270            if isinstance(relation, Join): 
     
    257275                self.order = [relation[key].qname for key in query.order] 
    258276     
    259     def _copy_column(self, table, alias, colkey): 
    260         """Copy the given column from the given table into our result table. 
    261          
    262         alias: the name that will be used for the given source table in the 
    263             FROM clause of the SQL SELECT statement. If None, this signifies 
    264             that the "SELECT columnlist FROM table" only references a single 
    265             table, in which case the column list will not use dotted column 
    266             names. 
     277    def result_table(self, name): 
     278        """Return a new Table object for the result of this select. 
     279         
     280        This is too expensive to do when you don't need it, so it's 
     281        a separate function here. Try not to call it more than once 
     282        for a given SelectWriter instance. 
    267283        """ 
    268         col = table[colkey] 
    269         newcol = col.copy() 
    270         newcol.key = False 
    271         newcol.autoincrement = False 
    272         newcol.sequence_name = None 
    273         newcol.initial = 1 
    274          
    275         if alias is None: 
    276             # Single table. 
    277             selname = col.qname 
    278         else: 
    279             selname = '%s.%s' % (alias, col.qname) 
    280         if colkey in self.result: 
    281             # Get the key for the table. 
    282             colkey = '%s_%s' % (table.schema.key_for(table), colkey) 
    283             newcol.name = '%s_%s' % (table.name, newcol.name) 
    284             newcol.qname = table.schema.db.quote(newcol.name) 
    285             selname = '%s AS %s' % (selname, newcol.qname) 
    286         self.input_list.append(selname) 
    287         self.output_list.append(newcol.qname) 
    288         self.output_keys.append(colkey) 
    289         self.result[colkey] = newcol 
     284        newtable = self.schema.table(name) 
     285        for colkey, name, qname in self.output: 
     286            col = self.output_cols[colkey] 
     287            newcol = col.copy() 
     288            newcol.name = name 
     289            newcol.qname = qname 
     290            newcol.key = False 
     291            newcol.autoincrement = False 
     292            newcol.sequence_name = None 
     293            newcol.initial = 1 
     294            newtable[colkey] = newcol 
     295        return newtable 
    290296     
    291297    def sql(self, distinct=False, limit=None, into=""): 
     
    302308            append("FROM") 
    303309            append(self.fromclause) 
    304         if self.whereclause: 
    305             append("WHERE") 
    306             append(self.whereclause) 
     310            if self.whereclause: 
     311                append("WHERE") 
     312                append(self.whereclause) 
    307313        if self._groupby and len(self._groupby) < len(self.input_list): 
    308314            append("GROUP BY") 
     
    318324        """Return an SQL WHERE clause, and an 'imperfect' flag.""" 
    319325        tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 
    320         decom = self.db.decompiler(tpairs, self.query.restriction, self.db.typeset) 
    321         code = decom.code() 
    322         return code, decom.imperfect 
     326        dep = self.db.deparser(tpairs, self.query.restriction, self.db.typeset) 
     327##        dep.verbose = True 
     328        code = dep.code() 
     329        return code, dep.imperfect 
    323330     
    324331    def wrap(self, join): 
     
    372379        if isinstance(path, logic.Expression): 
    373380            tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 
    374             decom = self.db.decompiler(tpairs, path, self.db.typeset) 
    375             decom.verbose = True 
    376             decom.walk() 
    377             print decom.stack 
    378             atom = decom.stack[0] 
    379             from geniusql import decompile 
    380             if atom is decompile.cannot_represent: 
    381                 raise ValueError("The supplied expression could not be " 
    382                                  "translated to SQL: %r" % path) 
     381            dep = self.db.deparser(tpairs, path, self.db.typeset) 
     382##            dep.verbose = True 
     383            dep.walk() 
     384            atom = dep.stack[0] 
    383385            return atom.sql 
    384386        else: 
     
    425427                                    % (name1, name2)) 
    426428     
    427     def decompile_attributes(self): 
     429    def deparse_attributes(self): 
    428430        tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 
    429         decom = self.db.decompiler 
    430         decom = decom(tpairs, self.query.attributes, self.db.typeset) 
    431 ##        decom.verbose = True 
    432          
    433         from geniusql import objects, decompile 
    434         for atom in decom.field_list(): 
    435             if atom is decompile.cannot_represent: 
    436                 raise ValueError("The supplied expression could not be " 
    437                                  "translated to SQL: %r" % 
    438                                  self.query.attributes) 
    439              
    440             if atom.name in self.result: 
     431        dep = self.db.deparser 
     432        dep = dep(tpairs, self.query.attributes, self.db.typeset) 
     433##        dep.verbose = True 
     434         
     435        for atom in dep.field_list(): 
     436            if atom.name in self.output_cols: 
    441437                bare_name = atom.name 
    442438                index = 1 
    443                 while atom.name in self.result
     439                while atom.name in self.output_cols
    444440                    atom.name = '%s%s' % (bare_name, index) 
    445441                    index += 1 
     
    447443            qname = self.db.quote(atom.name) 
    448444            self.input_list.append('%s AS %s' % (atom.sql, qname)) 
    449             self.output_keys.append(atom.name) 
    450             self.output_list.append(qname) 
     445            self.output.append((atom.name, atom.name, qname)) 
    451446            if not atom.aggregate: 
    452447                self._groupby.append(atom.sql) 
    453448             
    454             col = objects.Column(atom.pytype, atom.dbtype, name=atom.name) 
    455             col.qname = self.db.quote(col.name) 
    456             col.adapter = atom.adapter 
    457             self.result[atom.name] = col 
    458      
    459     def decompile_order(self): 
     449            self.output_cols[atom.name] = atom 
     450     
     451    def deparse_order(self): 
    460452        tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 
    461         decom = self.db.decompiler 
    462         decom = decom(tpairs, self.query.order, self.db.typeset) 
    463 ##        decom.verbose = True 
    464          
    465         from geniusql import decompile 
    466         ob = [] 
    467         for atom in decom.field_list(): 
    468             if atom is decompile.cannot_represent: 
    469                 raise ValueError("The supplied expression could not be " 
    470                                  "translated to SQL: %r" % 
    471                                  self.query.order) 
    472             ob.append(atom.sql) 
    473          
    474         self.order = ob 
     453        dep = self.db.deparser 
     454        dep = dep(tpairs, self.query.order, self.db.typeset) 
     455##        dep.verbose = True 
     456        self.order = [atom.sql for atom in dep.field_list()] 
  • trunk/geniusql/test/benchmark.py

    r134 r135  
    1818        Animal['ID'] = schema.column(int, autoincrement=True, key=True) 
    1919        Animal['ZooID'] = schema.column(int) 
     20        Animal.add_index('ZooID') 
    2021        Animal['Name'] = schema.column(hints={'bytes': 100}) 
    2122        Animal['Species'] = schema.column(hints={'bytes': 100}) 
     
    2627        Animal['PreferredFoodID'] = schema.column(int) 
    2728        Animal['AlternateFoodID'] = schema.column(int) 
    28         Animal.add_index('ZooID') 
    2929        Animal.references['Animal'] = ('ID', 'Animal', 'MotherID') 
    3030        schema['Animal'] = Animal 
     
    3333        Zoo['ID'] = schema.column(int, autoincrement=True, key=True) 
    3434        Zoo.add_index('ID') 
    35         Zoo['Name'] = schema.column(
     35        Zoo['Name'] = schema.column(hints={'bytes': 255}
    3636        Zoo['Founded'] = schema.column(datetime.date) 
    3737        Zoo['Opens'] = schema.column(datetime.time) 
     
    129129         
    130130        for x in xrange(ITERATIONS): 
    131             assert len(Zoo.select_all()) == 5 
     131            allzoos = Zoo.select_all() 
     132            assert len(allzoos) == 5, allzoos 
    132133            assert len(Animal.select_all(lambda x: True)) == ITERATIONS + 12 
    133134            assert len(Animal.select_all(lambda x: x.Legs == 4)) == 4 
     
    267268         
    268269        zm = ZooMark() 
    269 ##        from cherrypy.lib import profiler 
    270 ##        p = profiler.Profiler(thisdir) 
     270        if profile: 
     271            from cherrypy.lib import profiler 
     272            p = profiler.Profiler(thisdir) 
    271273        for method in [x for x in dir(zm) if x.startswith("step_")]: 
    272274            startTime = datetime.datetime.now() 
    273275            meth = getattr(zm, method) 
    274 ##            p.run(method) 
    275             meth() 
     276            if profile: 
     277                p.run(meth) 
     278            else: 
     279                meth() 
    276280            print "Ran %s in: %s" % (method, datetime.datetime.now() - startTime) 
    277281    finally: 
     
    288292    args = sys.argv[1:] 
    289293    if args: 
    290         ITERATIONS = int(args[0]) 
     294        ITERATIONS = int(args[-1]) 
     295        profile = "--profile" in args 
    291296     
    292297    run("psycopg", "geniusql_bench", 
  • trunk/geniusql/test/test_logic.py

    r134 r135  
    1717        e = logic.Expression(lambda x: icontains(x.Status, 'c')) 
    1818        self.assertEqual(repr(e), lx + "icontains(x.Status, 'c'))") 
    19         co_code = e.func.func_code.co_code 
    20         co_code = logic.codewalk.named_opcodes(map(ord, co_code)) 
    21         if sys.version_info >= (2, 5): 
    22             # Python 2.5 stopped including args in co_names, 
    23             # so the indices into co_names changed. 
    24             self.assertEqual(co_code, 
    25                              ['LOAD_CONST', 2, 0, 
    26                               'LOAD_FAST', 0, 0, 
    27                               'LOAD_ATTR', 2, 0, 
    28                               'LOAD_CONST', 0, 0, 
    29                               'CALL_FUNCTION', 2, 0, 
    30                               'RETURN_VALUE']) 
    31         else: 
    32             self.assertEqual(co_code, 
    33                              ['LOAD_CONST', 2, 0, 
    34                               'LOAD_FAST', 0, 0, 
    35                               'LOAD_ATTR', 2, 0, 
    36                               'LOAD_CONST', 1, 0, 
    37                               'CALL_FUNCTION', 2, 0, 
    38                               'RETURN_VALUE']) 
    3919         
    4020        # 4/28/04: This one failed in endue.html.nav, 
     
    240220        f = logic.comparison('Name', 2, 'Harry') 
    241221        g = logic.Expression(lambda x: x.Name == 'Harry') 
    242         self.assertEqual(f.func.func_code, g.func.func_code) 
     222        self.assertEqual(f.func.func_code.co_code, g.func.func_code.co_code) 
    243223         
    244224        f = logic.comparison('Size', 4, 300) 
    245225        g = logic.Expression(lambda x: x.Size > 300) 
    246         self.assertEqual(f.func.func_code, g.func.func_code) 
     226        self.assertEqual(f.func.func_code.co_code, g.func.func_code.co_code) 
    247227         
    248228        f = logic.comparison(u'ID', 2, u'30003') 
    249229        g = logic.Expression(lambda x: x.ID == u'30003') 
    250         self.assertEqual(f.func.func_code, g.func.func_code) 
     230        self.assertEqual(f.func.func_code.co_code, g.func.func_code.co_code) 
    251231 
    252232 
  • trunk/geniusql/test/zoo_fixture.py

    r134 r135  
    386386        self.assertEqual(matches(lambda x: day(x.LastEscape) == 21), 1) 
    387387         
    388         # Test AND, OR with cannot_represent. 
     388        # Test AND, OR with CannotRepresent. 
    389389        # Notice that we reference a method ('count') which no 
    390390        # known SM handles, so it will default back to Expr.eval().