Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 34

Show
Ignore:
Timestamp:
03/15/07 12:59:00
Author:
fumanchu
Message:

Split decompilation from select module into its own decompile module.

Files:

Legend:

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

    r33 r34  
    113113from geniusql import adapters 
    114114from geniusql import conns 
     115from geniusql import decompile 
    115116from geniusql import isolation 
    116117from geniusql import select 
  • trunk/geniusql/objects.py

    r33 r34  
    66from geniusql import adapters 
    77from geniusql import conns 
     8from geniusql import decompile 
    89from geniusql import isolation 
    910from geniusql import select 
    1011 
     12__all__ = [ 
     13    'Index', 'IndexSet', 'Column', 'Table', 'Schema', 'Database', 'Dataset', 
     14    ] 
    1115 
    1216class Bijection(dict): 
     
    751755    adapterfromdb = adapters.AdapterFromDB() 
    752756    typeadapter = adapters.TypeAdapter() 
    753     decompiler = select.SQLDecompiler 
     757    decompiler = decompile.SQLDecompiler 
    754758    joinwrapper = select.TableWrapper 
    755759    selectwriter = select.SelectWriter 
  • trunk/geniusql/providers/ado.py

    r33 r34  
    3737 
    3838import geniusql 
    39 from geniusql import adapters, conns, errors, select, typerefs 
     39from geniusql import adapters, conns, decompile, errors, select, typerefs 
    4040from geniusql import isolation as _isolation 
    4141 
     
    231231 
    232232 
    233 class ADOSQLDecompiler(select.SQLDecompiler): 
     233class ADOSQLDecompiler(decompile.SQLDecompiler): 
    234234     
    235235    def visit_COMPARE_OP(self, lo, hi): 
    236236        op2, op1 = self.stack.pop(), self.stack.pop() 
    237         if op1 is select.cannot_represent or op2 is select.cannot_represent: 
    238             self.stack.append(select.cannot_represent) 
     237        if op1 is decompile.cannot_represent or op2 is decompile.cannot_represent: 
     238            self.stack.append(decompile.cannot_represent) 
    239239            return 
    240240         
     
    268268                op1, op2 = self._compare_constants(op1, op2) 
    269269            except TypeError: 
    270                 self.stack.append(select.cannot_represent) 
     270                self.stack.append(decompile.cannot_represent) 
    271271                self.imperfect = True 
    272272                return 
    273273             
    274             if (isinstance(op2, select.ConstWrapper) 
     274            if (isinstance(op2, decompile.ConstWrapper) 
    275275                and isinstance(op2.basevalue, basestring)): 
    276276                atom = self._compare_strings(op1, op, op2) 
     
    301301    def containedby(self, op1, op2): 
    302302        self.imperfect = True 
    303         if isinstance(op1, select.ConstWrapper): 
     303        if isinstance(op1, decompile.ConstWrapper): 
    304304            # Looking for text in a field. Use Like (reverse terms). 
    305305            return op2 + " LIKE '%" + self.adapter.escape_like(op1) + "%'" 
     
    314314     
    315315    def dejavu_icontainedby(self, op1, op2): 
    316         if isinstance(op1, select.ConstWrapper): 
     316        if isinstance(op1, decompile.ConstWrapper): 
    317317            # Looking for text in a field. Use Like (reverse terms). 
    318318            # LIKE is already case-insensitive in MS SQL Server; 
  • trunk/geniusql/providers/firebird.py

    r33 r34  
    77 
    88import geniusql 
    9 from geniusql import adapters, conns, errors, select, typerefs 
     9from geniusql import adapters, conns, decompile, errors, select, typerefs 
    1010from geniusql import isolation as _isolation 
    1111 
     
    150150 
    151151 
    152 class FirebirdSQLDecompiler(select.SQLDecompiler): 
     152class FirebirdSQLDecompiler(decompile.SQLDecompiler): 
    153153     
    154154    # --------------------------- Dispatchees --------------------------- # 
     
    161161     
    162162    def containedby(self, op1, op2): 
    163         if isinstance(op1, select.ConstWrapper): 
     163        if isinstance(op1, decompile.ConstWrapper): 
    164164            # Looking for text in a field. Use Like (reverse terms). 
    165165            return op2 + " LIKE '%" + self.adapter.escape_like(op1) + "%' ESCAPE '\\'" 
     
    174174     
    175175    def dejavu_icontainedby(self, op1, op2): 
    176         if isinstance(op1, select.ConstWrapper): 
     176        if isinstance(op1, decompile.ConstWrapper): 
    177177            return op2 + " CONTAINING " + op1 
    178178        else: 
  • trunk/geniusql/providers/mysql.py

    r33 r34  
    1717 
    1818import geniusql 
    19 from geniusql import adapters, conns, errors, providers, select, typerefs 
     19from geniusql import adapters, conns, decompile, errors, providers, typerefs 
    2020 
    2121 
     
    5050 
    5151 
    52 class MySQLDecompiler(select.SQLDecompiler): 
     52class MySQLDecompiler(decompile.SQLDecompiler): 
    5353     
    5454    def dejavu_today(self): 
     
    6363     
    6464    def dejavu_icontainedby(self, op1, op2): 
    65         if isinstance(op1, select.ConstWrapper): 
     65        if isinstance(op1, decompile.ConstWrapper): 
    6666            # Looking for text in a field. Use Like (reverse terms). 
    6767            return ("CONVERT("+ op2 + " USING utf8) LIKE '%" + 
  • trunk/geniusql/providers/postgres.py

    r33 r34  
    1313 
    1414import geniusql 
    15 from geniusql import adapters, errors, select, typerefs 
     15from geniusql import adapters, decompile, errors, typerefs 
    1616 
    1717 
     
    8181 
    8282 
    83 class PgDecompiler(select.SQLDecompiler): 
     83class PgDecompiler(decompile.SQLDecompiler): 
    8484     
    8585    def dejavu_icontainedby(self, op1, op2): 
    86         if isinstance(op1, select.ConstWrapper): 
     86        if isinstance(op1, decompile.ConstWrapper): 
    8787            # Looking for text in a field. Use ILike (reverse terms). 
    8888            return op2 + " ILIKE '%" + self.adapter.escape_like(op1) + "%'" 
  • trunk/geniusql/providers/sqlite.py

    r33 r34  
    44 
    55import geniusql 
    6 from geniusql import adapters, conns, errors, providers, select, typerefs 
     6from geniusql import adapters, conns, decompile, errors, providers, select, typerefs 
    77from geniusql import isolation as _isolation 
    88 
     
    6969 
    7070 
    71 class SQLiteDecompiler(select.SQLDecompiler): 
     71class SQLiteDecompiler(decompile.SQLDecompiler): 
    7272     
    7373    def visit_COMPARE_OP(self, lo, hi): 
     
    8282            # comparison. 
    8383            for i, op in enumerate((op1, op2)): 
    84                 if (isinstance(op, select.ConstWrapper) and 
     84                if (isinstance(op, decompile.ConstWrapper) and 
    8585                    isinstance(op.basevalue, (datetime.date, datetime.datetime))): 
    8686                    op1 = "julianday(" + op1 + ")" 
     
    9999            self.stack.append(op2) 
    100100         
    101         select.SQLDecompiler.visit_COMPARE_OP(self, lo, hi) 
     101        decompile.SQLDecompiler.visit_COMPARE_OP(self, lo, hi) 
    102102     
    103103    def attr_startswith(self, tos, arg): 
     
    120120     
    121121    def containedby(self, op1, op2): 
    122         if isinstance(op1, select.ConstWrapper): 
     122        if isinstance(op1, decompile.ConstWrapper): 
    123123            # Looking for text in a field. Use Like (reverse terms). 
    124124            if _escape_support: 
     
    135135     
    136136    def dejavu_icontainedby(self, op1, op2): 
    137         if isinstance(op1, select.ConstWrapper): 
     137        if isinstance(op1, decompile.ConstWrapper): 
    138138            # Looking for text in a field. Use Like (reverse terms). 
    139139            if _escape_support: 
     
    180180        else: 
    181181            self.imperfect = True 
    182             return select.cannot_represent 
     182            return decompile.cannot_represent 
    183183     
    184184    dejavu_today = dejavu_now 
     
    189189        else: 
    190190            self.imperfect = True 
    191             return select.cannot_represent 
     191            return decompile.cannot_represent 
    192192     
    193193    def dejavu_month(self, x): 
     
    196196        else: 
    197197            self.imperfect = True 
    198             return select.cannot_represent 
     198            return decompile.cannot_represent 
    199199     
    200200    def dejavu_day(self, x): 
     
    203203        else: 
    204204            self.imperfect = True 
    205             return select.cannot_represent 
     205            return decompile.cannot_represent 
    206206 
    207207 
  • trunk/geniusql/select.py

    r33 r34  
    1 from types import FunctionType 
    2  
    31from geniusql import errors 
    4 from dejavu import logic, codewalk 
    5  
    6  
    7 __all__ = [ 
    8     # Decompilation 
    9     'ConstWrapper', 'ColumnWrapper', 'Sentinel', 
    10     'cannot_represent', 'kw_arg', 'SQLDecompiler', 
    11      
    12     # Select (including Join) 
    13     'TableWrapper', 'Join', 'SelectWriter', 
    14     ] 
    15  
    16  
    17 # -------------------------- SQL DECOMPILATION -------------------------- # 
    18  
    19  
    20 class ConstWrapper(str): 
    21     """Wraps a constant for use in SQLDecompiler's stack. 
    22      
    23     When we hit LOAD_CONST while decompiling, we occasionally need to keep 
    24     both the base and the coerced value around (see COMPARE_OP for use 
    25     of ConstWrapper.basevalue). 
    26     """ 
    27     def __new__(self, basevalue, coerced_value): 
    28         newobj = str.__new__(ConstWrapper, coerced_value) 
    29         newobj.basevalue = basevalue 
    30         return newobj 
    31  
    32  
    33 class ColumnWrapper(str): 
    34     """Wraps a column for use in SQLDecompiler's stack. 
    35      
    36     When we hit LOAD_ATTR while decompiling, we occasionally need to keep 
    37     the column type around (see COMPARE_OP for use of ColumnWrapper.type). 
    38     """ 
    39     def __new__(self, value, col): 
    40         newobj = str.__new__(ColumnWrapper, value) 
    41         newobj.col = col 
    42         return newobj 
    43  
    44  
    45 # Stack sentinels 
    46 class Sentinel(object): 
    47      
    48     def __init__(self, name): 
    49         self.name = name 
    50      
    51     def __repr__(self): 
    52         return 'Stack Sentinel: %s' % self.name 
    53  
    54 kw_arg = Sentinel('Keyword Arg') 
    55 # cannot_represent exists so that a portion of an Expression can be 
    56 # labeled imperfect. For example, the function "iscurrentweek" 
    57 # rarely has an SQL equivalent. All rows (which match the rest of the 
    58 # Expression) will be recalled; they can then be compared in expr(unit). 
    59 cannot_represent = Sentinel('Cannot Repr') 
    60  
    61  
    62 class SQLDecompiler(codewalk.LambdaDecompiler): 
    63     """Produce SQL from a supplied Expression object. 
    64      
    65     Attributes of each argument in the signature will be mapped to table 
    66     columns. Keyword arguments should be bound using Expression.bind_args 
    67     before calling this decompiler. 
    68     """ 
    69      
    70     # Some constants are function or class objects, 
    71     # which should not be coerced. 
    72     no_coerce = (FunctionType, 
    73                  type, 
    74                  type(len),       # <type 'builtin_function_or_method'> 
    75                  ) 
    76      
    77     sql_cmp_op = ('<', '<=', '=', '!=', '>', '>=', 'in', 'not in') 
    78      
    79     def __init__(self, tables, expr, adapter): 
    80         self.tables = tables 
    81         self.expr = expr 
    82         self.adapter = adapter 
    83         # Cache coerced booleans 
    84         self.T = adapter.coerce_bool_to_any(True) 
    85         self.F = adapter.coerce_bool_to_any(False) 
    86         obj = expr.func 
    87         codewalk.LambdaDecompiler.__init__(self, obj) 
    88      
    89     def code(self): 
    90         self.imperfect = False 
    91         self.walk() 
    92         # After walk(), self.stack should be reduced to a single string, 
    93         # which is the SQL representation of our Expression. 
    94         result = self.stack[0] 
    95         if result is cannot_represent: 
    96             # The entire expression could not be evaluated. 
    97             result = self.adapter.bool_true 
    98         if result == self.T: 
    99             result = self.adapter.bool_true 
    100         if result == self.F: 
    101             result = self.adapter.bool_false 
    102         return result 
    103      
    104     def visit_instruction(self, op, lo=None, hi=None): 
    105         # Get the instruction pointer for the current instruction. 
    106         ip = self.cursor - 3 
    107         if hi is None: 
    108             ip += 1 
    109             if lo is None: 
    110                 ip += 1 
    111          
    112         terms = self.targets.get(ip) 
    113         if terms: 
    114             trueval = self.adapter.bool_true 
    115             falseval = self.adapter.bool_false 
    116             clause = self.stack[-1] 
    117             while terms: 
    118                 term, oper = terms.pop() 
    119                 if term is cannot_represent: 
    120                     # Use TRUE for the term, so all records are returned. 
    121                     term = trueval 
    122                 if clause is cannot_represent: 
    123                     # Use TRUE for the clause, so all records are returned. 
    124                     clause = trueval 
    125                  
    126                 # Blurg. SQL Server is *so* picky. 
    127                 if term == self.T: 
    128                     term = trueval 
    129                 elif term == self.F: 
    130                     term = falseval 
    131                 if clause == self.T: 
    132                     clause = trueval 
    133                 elif clause == self.F: 
    134                     clause = falseval 
    135                  
    136                 clause = "(%s) %s (%s)" % (term, oper.upper(), clause) 
    137              
    138             # Replace TOS with the new clause, so that further 
    139             # combinations have access to it. 
    140             self.stack[-1] = clause 
    141             if self.verbose: 
    142                 self.debug("clause:", clause, "\n") 
    143              
    144             if op == 1: 
    145                 # Py2.4: The current instruction is POP_TOP, which means 
    146                 # the previous is probably JUMP_*. If so, we're going to 
    147                 # pop the value we just placed on the stack and lose it. 
    148                 # We need to replace the entry that the JUMP_* made in 
    149                 # self.targets with our new TOS. 
    150                 target = self.targets[self.last_target_ip] 
    151                 target[-1] = ((clause, target[-1][1])) 
    152                 if self.verbose: 
    153                     self.debug("newtarget:", self.last_target_ip, target) 
    154      
    155     def visit_LOAD_DEREF(self, lo, hi): 
    156         raise ValueError("Illegal reference found in %s." % self.expr) 
    157      
    158     def visit_LOAD_GLOBAL(self, lo, hi): 
    159         raise ValueError("Illegal global found in %s." % self.expr) 
    160      
    161     def visit_LOAD_FAST(self, lo, hi): 
    162         arg_index = lo + (hi << 8) 
    163         if arg_index < self.co_argcount: 
    164             # We've hit a reference to a positional arg, which in our case 
    165             # implies a reference to a DB table. Append the (qname, table) 
    166             # tuple for later unpacking inside visit_LOAD_ATTR. 
    167             self.stack.append(self.tables[arg_index]) 
    168         else: 
    169             # Since lambdas don't support local bindings, 
    170             # any remaining local name must be a keyword arg. 
    171             self.stack.append(kw_arg) 
    172      
    173     def visit_LOAD_ATTR(self, lo, hi): 
    174         name = self.co_names[lo + (hi << 8)] 
    175         tos = self.stack.pop() 
    176         if isinstance(tos, tuple): 
    177             # The name in question refers to a DB column (see visit_LOAD_FAST). 
    178             alias, table = tos 
    179             col = table[name] 
    180             atom = ColumnWrapper('%s.%s' % (alias, col.qname), col) 
    181         else: 
    182             # 'tos.name' will reference an attribute of the tos object. 
    183             # Stick the tos and name in a tuple for later processing 
    184             # (for example, in visit_CALL_FUNCTION). 
    185             atom = (tos, name) 
    186         self.stack.append(atom) 
    187      
    188     def visit_LOAD_CONST(self, lo, hi): 
    189         val = self.co_consts[lo + (hi << 8)] 
    190         if not isinstance(val, self.no_coerce): 
    191             val = ConstWrapper(val, self.adapter.coerce(val)) 
    192         self.stack.append(val) 
    193      
    194     def visit_BUILD_TUPLE(self, lo, hi): 
    195         terms = ", ".join([self.stack.pop() for i in range(lo + hi << 8)]) 
    196         self.stack.append("(" + terms + ")") 
    197      
    198     visit_BUILD_LIST = visit_BUILD_TUPLE 
    199      
    200     def visit_CALL_FUNCTION(self, lo, hi): 
    201         kwargs = {} 
    202         for i in xrange(hi): 
    203             val = self.stack.pop() 
    204             key = self.stack.pop() 
    205             kwargs[key] = val 
    206         kwargs = [k + "=" + v for k, v in kwargs.iteritems()] 
    207          
    208         args = [] 
    209         for i in xrange(lo): 
    210             arg = self.stack.pop() 
    211             args.append(arg) 
    212         args.reverse() 
    213          
    214         if kwargs: 
    215             args += kwargs 
    216          
    217         func = self.stack.pop() 
    218          
    219         # Handle function objects. 
    220         if isinstance(func, tuple): 
    221             tos, name = func 
    222             dispatch = getattr(self, "attr_" + name, None) 
    223             if dispatch: 
    224                 self.stack.append(dispatch(tos, *args)) 
    225                 return 
    226         else: 
    227             funcname = func.__module__ + "_" + func.__name__ 
    228             funcname = funcname.replace(".", "_") 
    229             if funcname.startswith("_"): 
    230                 funcname = "func" + funcname 
    231             dispatch = getattr(self, funcname, None) 
    232             if dispatch: 
    233                 self.stack.append(dispatch(*args)) 
    234                 return 
    235          
    236         self.stack.append(cannot_represent) 
    237         self.imperfect = True 
    238      
    239     def visit_COMPARE_OP(self, lo, hi): 
    240         op2, op1 = self.stack.pop(), self.stack.pop() 
    241         if op1 is cannot_represent or op2 is cannot_represent: 
    242             self.stack.append(cannot_represent) 
    243             return 
    244          
    245         op = lo + (hi << 8) 
    246         if op in (6, 7):     # in, not in 
    247             value = self.containedby(op1, op2) 
    248             if op == 7: 
    249                 value = "NOT " + value 
    250             self.stack.append(value) 
    251         elif op1 == 'NULL': 
    252             if op in (2, 8):    # '==', is 
    253                 self.stack.append(op2 + " IS NULL") 
    254             elif op in (3, 9):  # '!=', 'is not' 
    255                 self.stack.append(op2 + " IS NOT NULL") 
    256             else: 
    257                 raise ValueError("Non-equality Null comparisons not allowed.") 
    258         elif op2 == 'NULL': 
    259             if op in (2, 8):    # '==', 'is' 
    260                 self.stack.append(op1 + " IS NULL") 
    261             elif op in (3, 9):  # '!=', 'is not' 
    262                 self.stack.append(op1 + " IS NOT NULL") 
    263             else: 
    264                 raise ValueError("Non-equality Null comparisons not allowed.") 
    265         else: 
    266             try: 
    267                 op1, op2 = self._compare_constants(op1, op2) 
    268             except TypeError: 
    269                 self.stack.append(cannot_represent) 
    270                 self.imperfect = True 
    271                 return 
    272             # Comparison operators for strings are case-sensitive in PG et al. 
    273             self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 
    274      
    275     def _compare_constants(self, op1, op2): 
    276         """Coerce/cast compared types. 
    277          
    278         If a column value is compared to a constant and no coerce or cast 
    279         adapter function is available, a TypeError is raised. 
    280         """ 
    281         col = getattr(op1, "col", None) 
    282         if col: 
    283             if isinstance(op2, ConstWrapper): 
    284                 if col.imperfect_type: 
    285                     # Try to cast the column to op2's type 
    286                     op1 = self.adapter.cast(op1, col.dbtype, 
    287                                             type(op2.basevalue)) 
    288                 else: 
    289                     # Try to coerce op2 to the column's type 
    290                     op2 = self.adapter.coerce(op2.basevalue, col.dbtype) 
    291         else: 
    292             col = getattr(op2, "col", None) 
    293             if col and isinstance(op1, ConstWrapper): 
    294                 if col.imperfect_type: 
    295                     # Try to cast the column to op1's type 
    296                     op2 = self.adapter.cast(op2, col.dbtype, 
    297                                             type(op1.basevalue)) 
    298                 else: 
    299                     # Try to coerce op1 to the column's type 
    300                     op1 = self.adapter.coerce(op1.basevalue, col.dbtype) 
    301         return op1, op2 
    302      
    303     def binary_op(self, op): 
    304         op2, op1 = self.stack.pop(), self.stack.pop() 
    305         self.stack.append(op1 + " " + op + " " + op2) 
    306      
    307     def visit_BINARY_SUBSCR(self): 
    308         # The only BINARY_SUBSCR used in Expressions should be kwargs[key]. 
    309         name = self.stack.pop() 
    310         tos = self.stack.pop() 
    311         if tos is not kw_arg: 
    312             raise ValueError("Subscript %s of %s object not allowed." 
    313                              % (name, tos)) 
    314         # name, since formed in LOAD_CONST, may have extraneous quotes. 
    315         name = name.strip("'\"") 
    316         value = self.expr.kwargs[name] 
    317         if not isinstance(value, self.no_coerce): 
    318             value = ConstWrapper(value, self.adapter.coerce(value)) 
    319         self.stack.append(value) 
    320      
    321     def visit_UNARY_NOT(self): 
    322         op = self.stack.pop() 
    323         if op is cannot_represent: 
    324             self.stack.append(cannot_represent) 
    325         else: 
    326             self.stack.append("NOT (" + op + ")") 
    327      
    328     # --------------------------- Dispatchees --------------------------- # 
    329      
    330     def attr_startswith(self, tos, arg): 
    331         return tos + " LIKE '" + self.adapter.escape_like(arg) + "%'" 
    332      
    333     def attr_endswith(self, tos, arg): 
    334         return tos + " LIKE '%" + self.adapter.escape_like(arg) + "'" 
    335      
    336     def containedby(self, op1, op2): 
    337         if isinstance(op1, ConstWrapper): 
    338             # Looking for text in a field. Use Like (reverse terms). 
    339             return op2 + " LIKE '%" + self.adapter.escape_like(op1) + "%'" 
    340         else: 
    341             # Looking for field in (a, b, c) 
    342             atoms = [self.adapter.coerce(x) for x in op2.basevalue] 
    343             if atoms: 
    344                 return op1 + " IN (" + ", ".join(atoms) + ")" 
    345             else: 
    346                 # Nothing will match the empty list, so return none. 
    347                 return self.adapter.bool_false 
    348      
    349     def dejavu_icontainedby(self, op1, op2): 
    350         if isinstance(op1, ConstWrapper): 
    351             # Looking for text in a field. Use Like (reverse terms). 
    352             return ("LOWER(" + op2 + ") LIKE '%" + 
    353                     self.adapter.escape_like(op1).lower() + "%'") 
    354         else: 
    355             # Looking for field in (a, b, c). 
    356             # Force all args to lowercase for case-insensitive comparison. 
    357             atoms = [self.adapter.coerce(x).lower() for x in op2.basevalue] 
    358             return "LOWER(%s) IN (%s)" % (op1, ", ".join(atoms)) 
    359      
    360     def dejavu_icontains(self, x, y): 
    361         return self.dejavu_icontainedby(y, x) 
    362      
    363     def dejavu_istartswith(self, x, y): 
    364         return "LOWER(" + x + ") LIKE '" + self.adapter.escape_like(y) + "%'" 
    365      
    366     def dejavu_iendswith(self, x, y): 
    367         return "LOWER(" + x + ") LIKE '%" + self.adapter.escape_like(y) + "'" 
    368      
    369     def dejavu_ieq(self, x, y): 
    370         return "LOWER(" + x + ") = LOWER(" + y + ")" 
    371      
    372     def dejavu_now(self): 
    373         return "NOW()" 
    374      
    375     def dejavu_today(self): 
    376         return "CURRENT_DATE" 
    377      
    378     def dejavu_year(self, x): 
    379         return "YEAR(" + x + ")" 
    380  
    381     def dejavu_month(self, x): 
    382         return "MONTH(" + x + ")" 
    383      
    384     def dejavu_day(self, x): 
    385         return "DAY(" + x + ")" 
    386      
    387     def func__builtin___len(self, x): 
    388         return "LENGTH(" + x + ")" 
    389  
     2from dejavu import logic 
     3 
     4 
     5__all__ = ['TableWrapper', 'Join', 'SelectWriter'] 
    3906 
    3917 
     
    707323        raise errors.ReferenceError("No reference found between %s and %s." 
    708324                                    % (name1, name2)) 
     325