Project

General

Profile

1 2211 aaronmk
# SQL code generation
2
3 2748 aaronmk
import copy
4 2276 aaronmk
import operator
5 2568 aaronmk
import re
6 2653 aaronmk
import UserDict
7 2953 aaronmk
import warnings
8 2276 aaronmk
9 2667 aaronmk
import dicts
10 2953 aaronmk
import exc
11 2701 aaronmk
import iters
12
import lists
13 2360 aaronmk
import objects
14 2222 aaronmk
import strings
15 2227 aaronmk
import util
16 2211 aaronmk
17 2587 aaronmk
##### Names
18 2499 aaronmk
19 2608 aaronmk
identifier_max_len = 63 # works for both PostgreSQL and MySQL
20 2587 aaronmk
21 2932 aaronmk
def concat(str_, suffix):
22 2609 aaronmk
    '''Preserves version so that it won't be truncated off the string, leading
23
    to collisions.'''
24 2613 aaronmk
    # Preserve version
25 2995 aaronmk
    match = re.match(r'^(.*?)((?:(?:#\d+)?\)?)*(?:\.\w+)?(?:::[\w ]+)*)$', str_)
26 2985 aaronmk
    if match:
27
        str_, old_suffix = match.groups()
28
        suffix = old_suffix+suffix
29 2613 aaronmk
30 2932 aaronmk
    return strings.concat(str_, suffix, identifier_max_len)
31 2587 aaronmk
32 2932 aaronmk
def truncate(str_): return concat(str_, '')
33 2842 aaronmk
34 2575 aaronmk
def is_safe_name(name):
35 2583 aaronmk
    '''A name is safe *and unambiguous* if it:
36
    * contains only *lowercase* word (\w) characters
37
    * doesn't start with a digit
38
    * contains "_", so that it's not a keyword
39 2984 aaronmk
    '''
40
    return re.match(r'^(?=.*_)(?!\d)[^\WA-Z]+$', name)
41 2568 aaronmk
42 2499 aaronmk
def esc_name(name, quote='"'):
43
    return quote + name.replace(quote, quote+quote) + quote
44
        # doubling an embedded quote escapes it in both PostgreSQL and MySQL
45
46 2513 aaronmk
def clean_name(name): return name.replace('"', '').replace('`', '')
47
48 3041 aaronmk
def esc_comment(comment): return '/*'+comment.replace('*/', '* /')+'*/'
49
50 2659 aaronmk
##### General SQL code objects
51 2219 aaronmk
52 2349 aaronmk
class MockDb:
53 2503 aaronmk
    def esc_value(self, value): return strings.repr_no_u(value)
54 2349 aaronmk
55 2499 aaronmk
    def esc_name(self, name): return esc_name(name)
56 2859 aaronmk
57
    def col_info(self, col):
58
        return TypedCol(col.name, '<type>', CustomCode('<default>'), True)
59
60 2349 aaronmk
mockDb = MockDb()
61
62 2514 aaronmk
class BasicObject(objects.BasicObject):
63
    def __init__(self, value): self.value = value
64
65
    def __str__(self): return clean_name(strings.repr_no_u(self))
66
67 2659 aaronmk
##### Unparameterized code objects
68
69 2514 aaronmk
class Code(BasicObject):
70 2658 aaronmk
    def to_str(self, db): raise NotImplementedError()
71 2349 aaronmk
72 2514 aaronmk
    def __repr__(self): return self.to_str(mockDb)
73 2211 aaronmk
74 2269 aaronmk
class CustomCode(Code):
75 2256 aaronmk
    def __init__(self, str_): self.str_ = str_
76
77
    def to_str(self, db): return self.str_
78
79 2815 aaronmk
def as_Code(value, db=None):
80
    '''
81
    @param db If set, runs db.std_code() on the value.
82
    '''
83
    if util.is_str(value):
84
        if db != None: value = db.std_code(value)
85
        return CustomCode(value)
86 2659 aaronmk
    else: return Literal(value)
87
88 2540 aaronmk
class Expr(Code):
89
    def __init__(self, expr): self.expr = expr
90
91
    def to_str(self, db): return '('+self.expr.to_str(db)+')'
92
93 3086 aaronmk
##### Names
94
95
class Name(Code):
96 3087 aaronmk
    def __init__(self, name):
97
        name = truncate(name)
98
99
        self.name = name
100 3086 aaronmk
101
    def to_str(self, db): return db.esc_name(self.name)
102
103
def as_Name(value):
104
    if isinstance(value, Code): return value
105
    else: return Name(value)
106
107 2335 aaronmk
##### Literal values
108
109 2216 aaronmk
class Literal(Code):
110 2211 aaronmk
    def __init__(self, value): self.value = value
111 2213 aaronmk
112
    def to_str(self, db): return db.esc_value(self.value)
113 2211 aaronmk
114 2400 aaronmk
def as_Value(value):
115
    if isinstance(value, Code): return value
116
    else: return Literal(value)
117
118 2216 aaronmk
def is_null(value): return isinstance(value, Literal) and value.value == None
119
120 2711 aaronmk
##### Derived elements
121
122
src_self = object() # tells Col that it is its own source column
123
124
class Derived(Code):
125
    def __init__(self, srcs):
126 2712 aaronmk
        '''An element which was derived from some other element(s).
127 2711 aaronmk
        @param srcs See self.set_srcs()
128
        '''
129
        self.set_srcs(srcs)
130
131 2713 aaronmk
    def set_srcs(self, srcs, overwrite=True):
132 2711 aaronmk
        '''
133
        @param srcs (self_type...)|src_self The element(s) this is derived from
134
        '''
135 2713 aaronmk
        if not overwrite and self.srcs != (): return # already set
136
137 2711 aaronmk
        if srcs == src_self: srcs = (self,)
138
        srcs = tuple(srcs) # make Col hashable
139
        self.srcs = srcs
140
141
    def _compare_on(self):
142
        compare_on = self.__dict__.copy()
143
        del compare_on['srcs'] # ignore
144
        return compare_on
145
146
def cols_srcs(cols): return lists.uniqify(iters.flatten((v.srcs for v in cols)))
147
148 2335 aaronmk
##### Tables
149
150 2712 aaronmk
class Table(Derived):
151 2991 aaronmk
    def __init__(self, name, schema=None, srcs=(), is_temp=False):
152 2211 aaronmk
        '''
153
        @param schema str|None (for no schema)
154 2712 aaronmk
        @param srcs (Table...)|src_self See Derived.set_srcs()
155 2211 aaronmk
        '''
156 2712 aaronmk
        Derived.__init__(self, srcs)
157
158 3091 aaronmk
        if util.is_str(name): name = truncate(name)
159 2843 aaronmk
160 2211 aaronmk
        self.name = name
161
        self.schema = schema
162 2991 aaronmk
        self.is_temp = is_temp
163 3000 aaronmk
        self.index_cols = {}
164 2211 aaronmk
165 2348 aaronmk
    def to_str(self, db):
166
        str_ = ''
167 3088 aaronmk
        if self.schema != None: str_ += as_Name(self.schema).to_str(db)+'.'
168
        str_ += as_Name(self.name).to_str(db)
169 2348 aaronmk
        return str_
170 2336 aaronmk
171
    def to_Table(self): return self
172 3000 aaronmk
173
    def _compare_on(self):
174
        compare_on = Derived._compare_on(self)
175
        del compare_on['index_cols'] # ignore
176
        return compare_on
177 2211 aaronmk
178 2835 aaronmk
def is_underlying_table(table):
179
    return isinstance(table, Table) and table.to_Table() is table
180 2832 aaronmk
181 2902 aaronmk
class NoUnderlyingTableException(Exception): pass
182
183
def underlying_table(table):
184
    table = remove_table_rename(table)
185
    if not is_underlying_table(table): raise NoUnderlyingTableException
186
    return table
187
188 2776 aaronmk
def as_Table(table, schema=None):
189 2270 aaronmk
    if table == None or isinstance(table, Code): return table
190 2776 aaronmk
    else: return Table(table, schema)
191 2219 aaronmk
192 3101 aaronmk
def suffixed_table(table, suffix):
193 3128 aaronmk
    table = copy.copy(table) # don't modify input!
194
    table.name = concat(table.name, suffix)
195
    return table
196 2707 aaronmk
197 2336 aaronmk
class NamedTable(Table):
198
    def __init__(self, name, code, cols=None):
199
        Table.__init__(self, name)
200
201 3016 aaronmk
        code = as_Table(code)
202 2741 aaronmk
        if not isinstance(code, (Table, FunctionCall, Expr)): code = Expr(code)
203 3020 aaronmk
        if cols != None: cols = [to_name_only_col(c).to_Col() for c in cols]
204 2336 aaronmk
205
        self.code = code
206
        self.cols = cols
207
208
    def to_str(self, db):
209 3026 aaronmk
        str_ = self.code.to_str(db)
210
        if str_.find('\n') >= 0: whitespace = '\n'
211
        else: whitespace = ' '
212
        str_ += whitespace+'AS '+Table.to_str(self, db)
213 2742 aaronmk
        if self.cols != None:
214
            str_ += ' ('+(', '.join((c.to_str(db) for c in self.cols)))+')'
215 2336 aaronmk
        return str_
216
217
    def to_Table(self): return Table(self.name)
218
219 2753 aaronmk
def remove_table_rename(table):
220
    if isinstance(table, NamedTable): table = table.code
221
    return table
222
223 2335 aaronmk
##### Columns
224
225 2711 aaronmk
class Col(Derived):
226 2701 aaronmk
    def __init__(self, name, table=None, srcs=()):
227 2211 aaronmk
        '''
228
        @param table Table|None (for no table)
229 2711 aaronmk
        @param srcs (Col...)|src_self See Derived.set_srcs()
230 2211 aaronmk
        '''
231 2711 aaronmk
        Derived.__init__(self, srcs)
232
233 3091 aaronmk
        if util.is_str(name): name = truncate(name)
234 2241 aaronmk
        if util.is_str(table): table = Table(table)
235 2211 aaronmk
        assert table == None or isinstance(table, Table)
236
237
        self.name = name
238
        self.table = table
239
240 2989 aaronmk
    def to_str(self, db, for_str=False):
241 3088 aaronmk
        str_ = as_Name(self.name).to_str(db)
242 2989 aaronmk
        if for_str: str_ = clean_name(str_)
243 2933 aaronmk
        if self.table != None:
244 2989 aaronmk
            table = self.table.to_Table()
245
            if for_str: str_ = concat(str(table), '.'+str_)
246
            else: str_ = table.to_str(db)+'.'+str_
247 2211 aaronmk
        return str_
248 2314 aaronmk
249 2989 aaronmk
    def __str__(self): return self.to_str(mockDb, for_str=True)
250 2933 aaronmk
251 2314 aaronmk
    def to_Col(self): return self
252 2211 aaronmk
253 2767 aaronmk
def is_table_col(col): return isinstance(col, Col) and col.table != None
254 2393 aaronmk
255 3000 aaronmk
def index_col(col):
256
    if not is_table_col(col): return None
257 3104 aaronmk
258
    table = col.table
259
    try: name = table.index_cols[col.name]
260
    except KeyError: return None
261
    else: return Col(name, table, col.srcs)
262 2999 aaronmk
263 3024 aaronmk
def is_temp_col(col): return is_table_col(col) and col.table.is_temp
264 2996 aaronmk
265 2563 aaronmk
def as_Col(col, table=None, name=None):
266
    '''
267
    @param name If not None, any non-Col input will be renamed using NamedCol.
268
    '''
269
    if name != None:
270
        col = as_Value(col)
271
        if not isinstance(col, Col): col = NamedCol(name, col)
272 2333 aaronmk
273
    if isinstance(col, Code): return col
274 2260 aaronmk
    else: return Col(col, table)
275
276 3093 aaronmk
def with_table(col, table):
277 3105 aaronmk
    if isinstance(col, NamedCol): pass # doesn't take a table
278
    elif isinstance(col, FunctionCall):
279
        col = copy.deepcopy(col) # don't modify input!
280
        col.args[0].table = table
281
    else:
282 3098 aaronmk
        col = copy.copy(col) # don't modify input!
283
        col.table = table
284 3093 aaronmk
    return col
285
286 3100 aaronmk
def with_default_table(col, table):
287 2747 aaronmk
    col = as_Col(col)
288 3100 aaronmk
    if col.table == None: col = with_table(col, table)
289 2747 aaronmk
    return col
290
291 2744 aaronmk
def set_cols_table(table, cols):
292
    table = as_Table(table)
293
294
    for i, col in enumerate(cols):
295
        col = cols[i] = as_Col(col)
296
        col.table = table
297
298 2401 aaronmk
def to_name_only_col(col, check_table=None):
299
    col = as_Col(col)
300 3020 aaronmk
    if not is_table_col(col): return col
301 2401 aaronmk
302
    if check_table != None:
303
        table = col.table
304
        assert table == None or table == check_table
305
    return Col(col.name)
306
307 2993 aaronmk
def suffixed_col(col, suffix):
308
    return Col(concat(col.name, suffix), col.table, col.srcs)
309
310 2323 aaronmk
class NamedCol(Col):
311 2229 aaronmk
    def __init__(self, name, code):
312 2310 aaronmk
        Col.__init__(self, name)
313
314 3016 aaronmk
        code = as_Value(code)
315 2229 aaronmk
316
        self.code = code
317
318
    def to_str(self, db):
319 2310 aaronmk
        return self.code.to_str(db)+' AS '+Col.to_str(self, db)
320 2314 aaronmk
321
    def to_Col(self): return Col(self.name)
322 2229 aaronmk
323 2462 aaronmk
def remove_col_rename(col):
324
    if isinstance(col, NamedCol): col = col.code
325
    return col
326
327 2830 aaronmk
def underlying_col(col):
328
    col = remove_col_rename(col)
329 2849 aaronmk
    if not isinstance(col, Col): raise NoUnderlyingTableException
330
331 2902 aaronmk
    return Col(col.name, underlying_table(col.table), col.srcs)
332 2830 aaronmk
333 2703 aaronmk
def wrap(wrap_func, value):
334
    '''Wraps a value, propagating any column renaming to the returned value.'''
335
    if isinstance(value, NamedCol):
336
        return NamedCol(value.name, wrap_func(value.code))
337
    else: return wrap_func(value)
338
339 2667 aaronmk
class ColDict(dicts.DictProxy):
340 2564 aaronmk
    '''A dict that automatically makes inserted entries Col objects'''
341
342 2645 aaronmk
    def __init__(self, db, keys_table, dict_={}):
343 2667 aaronmk
        dicts.DictProxy.__init__(self, {})
344
345 2645 aaronmk
        keys_table = as_Table(keys_table)
346
347 2642 aaronmk
        self.db = db
348 2641 aaronmk
        self.table = keys_table
349 2653 aaronmk
        self.update(dict_) # after setting vars because __setitem__() needs them
350 2641 aaronmk
351 2667 aaronmk
    def copy(self): return ColDict(self.db, self.table, self.inner.copy())
352 2655 aaronmk
353 2667 aaronmk
    def __getitem__(self, key):
354
        return dicts.DictProxy.__getitem__(self, self._key(key))
355 2653 aaronmk
356 2564 aaronmk
    def __setitem__(self, key, value):
357 2642 aaronmk
        key = self._key(key)
358 2819 aaronmk
        if value == None: value = self.db.col_info(key).default
359 2667 aaronmk
        dicts.DictProxy.__setitem__(self, key, as_Col(value, name=key.name))
360 2564 aaronmk
361 2641 aaronmk
    def _key(self, key): return as_Col(key, self.table)
362 2564 aaronmk
363 2524 aaronmk
##### Functions
364
365 2912 aaronmk
Function = Table
366 2911 aaronmk
as_Function = as_Table
367
368 2691 aaronmk
class InternalFunction(CustomCode): pass
369
370 2941 aaronmk
class NamedArg(NamedCol):
371
    def __init__(self, name, value):
372
        NamedCol.__init__(self, name, value)
373
374
    def to_str(self, db):
375
        return Col.to_str(self, db)+' := '+self.code.to_str(db)
376
377 2524 aaronmk
class FunctionCall(Code):
378 2941 aaronmk
    def __init__(self, function, *args, **kw_args):
379 2524 aaronmk
        '''
380 2690 aaronmk
        @param args [Code|literal-value...] The function's arguments
381 2524 aaronmk
        '''
382 3016 aaronmk
        function = as_Function(function)
383 2941 aaronmk
        def filter_(arg): return remove_col_rename(as_Value(arg))
384
        args = map(filter_, args)
385
        args += [NamedArg(k, filter_(v)) for k, v in kw_args.iteritems()]
386 2524 aaronmk
387
        self.function = function
388
        self.args = args
389
390
    def to_str(self, db):
391
        args_str = ', '.join((v.to_str(db) for v in self.args))
392
        return self.function.to_str(db)+'('+args_str+')'
393
394 2533 aaronmk
def wrap_in_func(function, value):
395
    '''Wraps a value inside a function call.
396
    Propagates any column renaming to the returned value.
397
    '''
398 2703 aaronmk
    return wrap(lambda v: FunctionCall(function, v), value)
399 2533 aaronmk
400 2561 aaronmk
def unwrap_func_call(func_call, check_name=None):
401
    '''Unwraps any function call to its first argument.
402
    Also removes any column renaming.
403
    '''
404
    func_call = remove_col_rename(func_call)
405
    if not isinstance(func_call, FunctionCall): return func_call
406
407
    if check_name != None:
408
        name = func_call.function.name
409
        assert name == None or name == check_name
410
    return func_call.args[0]
411
412 2986 aaronmk
##### Casts
413
414
class Cast(FunctionCall):
415
    def __init__(self, type_, value):
416
        value = as_Value(value)
417
418
        self.type_ = type_
419
        self.value = value
420
421
    def to_str(self, db):
422
        return 'CAST('+self.value.to_str(db)+' AS '+self.type_+')'
423
424 2335 aaronmk
##### Conditions
425 2259 aaronmk
426 2398 aaronmk
class ColValueCond(Code):
427
    def __init__(self, col, value):
428
        value = as_ValueCond(value)
429
430
        self.col = col
431
        self.value = value
432
433
    def to_str(self, db): return self.value.to_str(db, self.col)
434
435 2577 aaronmk
def combine_conds(conds, keyword=None):
436
    '''
437
    @param keyword The keyword to add before the conditions, if any
438
    '''
439
    str_ = ''
440
    if keyword != None:
441
        if conds == []: whitespace = ''
442
        elif len(conds) == 1: whitespace = ' '
443
        else: whitespace = '\n'
444
        str_ += keyword+whitespace
445
446
    str_ += '\nAND '.join(conds)
447
    return str_
448
449 2398 aaronmk
##### Condition column comparisons
450
451 2514 aaronmk
class ValueCond(BasicObject):
452 2213 aaronmk
    def __init__(self, value):
453 2858 aaronmk
        value = remove_col_rename(as_Value(value))
454 2213 aaronmk
455
        self.value = value
456 2214 aaronmk
457 2216 aaronmk
    def to_str(self, db, left_value):
458 2214 aaronmk
        '''
459 2216 aaronmk
        @param left_value The Code object that the condition is being applied on
460 2214 aaronmk
        '''
461
        raise NotImplemented()
462 2228 aaronmk
463 2514 aaronmk
    def __repr__(self): return self.to_str(mockDb, '<left_value>')
464 2211 aaronmk
465
class CompareCond(ValueCond):
466
    def __init__(self, value, operator='='):
467 2222 aaronmk
        '''
468
        @param operator By default, compares NULL values literally. Use '~=' or
469
            '~!=' to pass NULLs through.
470
        '''
471 2211 aaronmk
        ValueCond.__init__(self, value)
472
        self.operator = operator
473
474 2216 aaronmk
    def to_str(self, db, left_value):
475 2858 aaronmk
        left_value = remove_col_rename(as_Col(left_value))
476 2216 aaronmk
477 2222 aaronmk
        right_value = self.value
478
479
        # Parse operator
480 2216 aaronmk
        operator = self.operator
481 2222 aaronmk
        passthru_null_ref = [False]
482
        operator = strings.remove_prefix('~', operator, passthru_null_ref)
483
        neg_ref = [False]
484
        operator = strings.remove_prefix('!', operator, neg_ref)
485 2844 aaronmk
        equals = operator.endswith('=') # also includes <=, >=
486 2222 aaronmk
487 2825 aaronmk
        # Handle nullable columns
488
        check_null = False
489 2844 aaronmk
        if not passthru_null_ref[0]: # NULLs compare equal
490 2857 aaronmk
            try: left_value = ensure_not_null(db, left_value)
491 2844 aaronmk
            except ensure_not_null_excs: # fall back to alternate method
492
                check_null = equals and isinstance(right_value, Col)
493 2837 aaronmk
            else:
494 2857 aaronmk
                if isinstance(left_value, EnsureNotNull):
495
                    right_value = ensure_not_null(db, right_value,
496
                        left_value.type) # apply same function to both sides
497 2825 aaronmk
498 2844 aaronmk
        if equals and is_null(right_value): operator = 'IS'
499
500 2825 aaronmk
        left = left_value.to_str(db)
501
        right = right_value.to_str(db)
502
503 2222 aaronmk
        # Create str
504
        str_ = left+' '+operator+' '+right
505 2825 aaronmk
        if check_null:
506 2578 aaronmk
            str_ = '('+str_+' OR ('+left+' IS NULL AND '+right+' IS NULL))'
507
        if neg_ref[0]: str_ = 'NOT '+str_
508 2222 aaronmk
        return str_
509 2216 aaronmk
510 2260 aaronmk
# Tells as_ValueCond() to assume a non-ValueCond is a literal value
511
assume_literal = object()
512
513
def as_ValueCond(value, default_table=assume_literal):
514
    if not isinstance(value, ValueCond):
515
        if default_table is not assume_literal:
516 2748 aaronmk
            value = with_default_table(value, default_table)
517 2260 aaronmk
        return CompareCond(value)
518 2216 aaronmk
    else: return value
519 2219 aaronmk
520 2335 aaronmk
##### Joins
521
522 2352 aaronmk
join_same = object() # tells Join the left and right columns have the same name
523 2260 aaronmk
524 2353 aaronmk
# Tells Join the left and right columns have the same name and are never NULL
525
join_same_not_null = object()
526
527 2260 aaronmk
filter_out = object() # tells Join to filter out rows that match the join
528
529 2514 aaronmk
class Join(BasicObject):
530 2746 aaronmk
    def __init__(self, table, mapping={}, type_=None):
531 2260 aaronmk
        '''
532
        @param mapping dict(right_table_col=left_table_col, ...)
533 2352 aaronmk
            * if left_table_col is join_same: left_table_col = right_table_col
534 2353 aaronmk
              * Note that right_table_col must be a string
535
            * if left_table_col is join_same_not_null:
536
              left_table_col = right_table_col and both have NOT NULL constraint
537
              * Note that right_table_col must be a string
538 2260 aaronmk
        @param type_ None (for plain join)|str (e.g. 'LEFT')|filter_out
539
            * filter_out: equivalent to 'LEFT' with the query filtered by
540
              `table_pkey IS NULL` (indicating no match)
541
        '''
542
        if util.is_str(table): table = Table(table)
543
        assert type_ == None or util.is_str(type_) or type_ is filter_out
544
545
        self.table = table
546
        self.mapping = mapping
547
        self.type_ = type_
548
549 2749 aaronmk
    def to_str(self, db, left_table_):
550 2260 aaronmk
        def join(entry):
551
            '''Parses non-USING joins'''
552
            right_table_col, left_table_col = entry
553
554 2353 aaronmk
            # Switch order (right_table_col is on the left in the comparison)
555
            left = right_table_col
556
            right = left_table_col
557 2749 aaronmk
            left_table = self.table
558
            right_table = left_table_
559 2353 aaronmk
560 2747 aaronmk
            # Parse left side
561 2748 aaronmk
            left = with_default_table(left, left_table)
562 2747 aaronmk
563 2260 aaronmk
            # Parse special values
564 2747 aaronmk
            left_on_right = Col(left.name, right_table)
565
            if right is join_same: right = left_on_right
566 2353 aaronmk
            elif right is join_same_not_null:
567 2747 aaronmk
                right = CompareCond(left_on_right, '~=')
568 2260 aaronmk
569 2747 aaronmk
            # Parse right side
570 2353 aaronmk
            right = as_ValueCond(right, right_table)
571 2747 aaronmk
572
            return right.to_str(db, left)
573 2260 aaronmk
574 2265 aaronmk
        # Create join condition
575
        type_ = self.type_
576 2276 aaronmk
        joins = self.mapping
577 2746 aaronmk
        if joins == {}: join_cond = None
578
        elif type_ is not filter_out and reduce(operator.and_,
579 2460 aaronmk
            (v is join_same_not_null for v in joins.itervalues())):
580 2260 aaronmk
            # all cols w/ USING, so can use simpler USING syntax
581 2747 aaronmk
            cols = map(to_name_only_col, joins.iterkeys())
582
            join_cond = 'USING ('+(', '.join((c.to_str(db) for c in cols)))+')'
583 2757 aaronmk
        else: join_cond = combine_conds(map(join, joins.iteritems()), 'ON')
584 2260 aaronmk
585 2757 aaronmk
        if isinstance(self.table, NamedTable): whitespace = '\n'
586
        else: whitespace = ' '
587
588 2260 aaronmk
        # Create join
589
        if type_ is filter_out: type_ = 'LEFT'
590 2266 aaronmk
        str_ = ''
591
        if type_ != None: str_ += type_+' '
592 2757 aaronmk
        str_ += 'JOIN'+whitespace+self.table.to_str(db)
593
        if join_cond != None: str_ += whitespace+join_cond
594 2266 aaronmk
        return str_
595 2349 aaronmk
596 2514 aaronmk
    def __repr__(self): return self.to_str(mockDb, '<left_table>')
597 2424 aaronmk
598
##### Value exprs
599
600 3089 aaronmk
all_cols = CustomCode('*')
601
602 2737 aaronmk
default = CustomCode('DEFAULT')
603
604 3090 aaronmk
row_count = FunctionCall(InternalFunction('COUNT'), all_cols)
605 2674 aaronmk
606 3061 aaronmk
class Coalesce(FunctionCall):
607
    def __init__(self, *args):
608
        FunctionCall.__init__(self, InternalFunction('COALESCE'), *args)
609 3060 aaronmk
610 3062 aaronmk
class Nullif(FunctionCall):
611
    def __init__(self, *args):
612
        FunctionCall.__init__(self, InternalFunction('NULLIF'), *args)
613
614 2850 aaronmk
# See <http://www.postgresql.org/docs/8.3/static/datatype-numeric.html>
615 2958 aaronmk
null_sentinels = {
616
    'character varying': r'\N',
617
    'double precision': 'NaN',
618
    'integer': 2147483647,
619
    'text': r'\N',
620
    'timestamp with time zone': 'infinity'
621
}
622 2692 aaronmk
623 3061 aaronmk
class EnsureNotNull(Coalesce):
624 2850 aaronmk
    def __init__(self, value, type_):
625 3061 aaronmk
        Coalesce.__init__(self, as_Col(value),
626 2988 aaronmk
            Cast(type_, null_sentinels[type_]))
627 2850 aaronmk
628
        self.type = type_
629 3001 aaronmk
630
    def to_str(self, db):
631
        col = self.args[0]
632
        index_col_ = index_col(col)
633
        if index_col_ != None: return index_col_.to_str(db)
634 3061 aaronmk
        return Coalesce.to_str(self, db)
635 2850 aaronmk
636 2737 aaronmk
##### Table exprs
637
638
class Values(Code):
639
    def __init__(self, values):
640 2739 aaronmk
        '''
641
        @param values [...]|[[...], ...] Can be one or multiple rows.
642
        '''
643
        rows = values
644
        if len(values) >= 1 and not lists.is_seq(values[0]): # only one row
645
            rows = [values]
646
        for i, row in enumerate(rows):
647
            rows[i] = map(remove_col_rename, map(as_Value, row))
648 2737 aaronmk
649 2739 aaronmk
        self.rows = rows
650 2737 aaronmk
651
    def to_str(self, db):
652 2739 aaronmk
        def row_str(row):
653
            return '('+(', '.join((v.to_str(db) for v in row)))+')'
654
        return 'VALUES '+(', '.join(map(row_str, self.rows)))
655 2737 aaronmk
656 2740 aaronmk
def NamedValues(name, cols, values):
657 2745 aaronmk
    '''
658 3048 aaronmk
    @param cols None|[...]
659 2745 aaronmk
    @post `cols` will be changed to Col objects with the table set to `name`.
660
    '''
661 2834 aaronmk
    table = NamedTable(name, Values(values), cols)
662 3048 aaronmk
    if cols != None: set_cols_table(table, cols)
663 2834 aaronmk
    return table
664 2740 aaronmk
665 2674 aaronmk
##### Database structure
666
667
class TypedCol(Col):
668 2871 aaronmk
    def __init__(self, name, type_, default=None, nullable=True,
669
        constraints=None):
670 2818 aaronmk
        assert default == None or isinstance(default, Code)
671
672 2674 aaronmk
        Col.__init__(self, name)
673
674
        self.type = type_
675 2818 aaronmk
        self.default = default
676
        self.nullable = nullable
677 2871 aaronmk
        self.constraints = constraints
678 2674 aaronmk
679 2818 aaronmk
    def to_str(self, db):
680
        str_ = Col.to_str(self, db)+' '+self.type
681
        if not self.nullable: str_ += ' NOT NULL'
682
        if self.default != None: str_ += ' DEFAULT '+self.default.to_str(db)
683 2871 aaronmk
        if self.constraints != None: str_ += ' '+self.constraints
684 2818 aaronmk
        return str_
685 2674 aaronmk
686
    def to_Col(self): return Col(self.name)
687 2822 aaronmk
688 2840 aaronmk
ensure_not_null_excs = (NoUnderlyingTableException, KeyError)
689
690 2851 aaronmk
def ensure_not_null(db, col, type_=None):
691 2840 aaronmk
    '''
692 2855 aaronmk
    @param col If type_ is not set, must have an underlying column.
693 2851 aaronmk
    @param type_ If set, overrides the underlying column's type.
694 2840 aaronmk
    @return EnsureNotNull|Col
695
    @throws ensure_not_null_excs
696
    '''
697 2855 aaronmk
    nullable = True
698
    try: typed_col = db.col_info(underlying_col(col))
699
    except NoUnderlyingTableException:
700
        if type_ == None: raise
701
    else:
702
        if type_ == None: type_ = typed_col.type
703
        nullable = typed_col.nullable
704
705 2953 aaronmk
    if nullable:
706
        try: col = EnsureNotNull(col, type_)
707
        except KeyError, e:
708
            # Warn of no null sentinel for type, even if caller catches error
709
            warnings.warn(UserWarning(exc.str_(e)))
710
            raise
711
712 2840 aaronmk
    return col