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
    return Table(concat(table.name, suffix), table.schema)
194 2707 aaronmk
195 2336 aaronmk
class NamedTable(Table):
196
    def __init__(self, name, code, cols=None):
197
        Table.__init__(self, name)
198
199 3016 aaronmk
        code = as_Table(code)
200 2741 aaronmk
        if not isinstance(code, (Table, FunctionCall, Expr)): code = Expr(code)
201 3020 aaronmk
        if cols != None: cols = [to_name_only_col(c).to_Col() for c in cols]
202 2336 aaronmk
203
        self.code = code
204
        self.cols = cols
205
206
    def to_str(self, db):
207 3026 aaronmk
        str_ = self.code.to_str(db)
208
        if str_.find('\n') >= 0: whitespace = '\n'
209
        else: whitespace = ' '
210
        str_ += whitespace+'AS '+Table.to_str(self, db)
211 2742 aaronmk
        if self.cols != None:
212
            str_ += ' ('+(', '.join((c.to_str(db) for c in self.cols)))+')'
213 2336 aaronmk
        return str_
214
215
    def to_Table(self): return Table(self.name)
216
217 2753 aaronmk
def remove_table_rename(table):
218
    if isinstance(table, NamedTable): table = table.code
219
    return table
220
221 2335 aaronmk
##### Columns
222
223 2711 aaronmk
class Col(Derived):
224 2701 aaronmk
    def __init__(self, name, table=None, srcs=()):
225 2211 aaronmk
        '''
226
        @param table Table|None (for no table)
227 2711 aaronmk
        @param srcs (Col...)|src_self See Derived.set_srcs()
228 2211 aaronmk
        '''
229 2711 aaronmk
        Derived.__init__(self, srcs)
230
231 3091 aaronmk
        if util.is_str(name): name = truncate(name)
232 2241 aaronmk
        if util.is_str(table): table = Table(table)
233 2211 aaronmk
        assert table == None or isinstance(table, Table)
234
235
        self.name = name
236
        self.table = table
237
238 2989 aaronmk
    def to_str(self, db, for_str=False):
239 3088 aaronmk
        str_ = as_Name(self.name).to_str(db)
240 2989 aaronmk
        if for_str: str_ = clean_name(str_)
241 2933 aaronmk
        if self.table != None:
242 2989 aaronmk
            table = self.table.to_Table()
243
            if for_str: str_ = concat(str(table), '.'+str_)
244
            else: str_ = table.to_str(db)+'.'+str_
245 2211 aaronmk
        return str_
246 2314 aaronmk
247 2989 aaronmk
    def __str__(self): return self.to_str(mockDb, for_str=True)
248 2933 aaronmk
249 2314 aaronmk
    def to_Col(self): return self
250 2211 aaronmk
251 2767 aaronmk
def is_table_col(col): return isinstance(col, Col) and col.table != None
252 2393 aaronmk
253 3000 aaronmk
def index_col(col):
254
    if not is_table_col(col): return None
255 3104 aaronmk
256
    table = col.table
257
    try: name = table.index_cols[col.name]
258
    except KeyError: return None
259
    else: return Col(name, table, col.srcs)
260 2999 aaronmk
261 3024 aaronmk
def is_temp_col(col): return is_table_col(col) and col.table.is_temp
262 2996 aaronmk
263 2563 aaronmk
def as_Col(col, table=None, name=None):
264
    '''
265
    @param name If not None, any non-Col input will be renamed using NamedCol.
266
    '''
267
    if name != None:
268
        col = as_Value(col)
269
        if not isinstance(col, Col): col = NamedCol(name, col)
270 2333 aaronmk
271
    if isinstance(col, Code): return col
272 2260 aaronmk
    else: return Col(col, table)
273
274 3093 aaronmk
def with_table(col, table):
275 3105 aaronmk
    if isinstance(col, NamedCol): pass # doesn't take a table
276
    elif isinstance(col, FunctionCall):
277
        col = copy.deepcopy(col) # don't modify input!
278
        col.args[0].table = table
279
    else:
280 3098 aaronmk
        col = copy.copy(col) # don't modify input!
281
        col.table = table
282 3093 aaronmk
    return col
283
284 3100 aaronmk
def with_default_table(col, table):
285 2747 aaronmk
    col = as_Col(col)
286 3100 aaronmk
    if col.table == None: col = with_table(col, table)
287 2747 aaronmk
    return col
288
289 2744 aaronmk
def set_cols_table(table, cols):
290
    table = as_Table(table)
291
292
    for i, col in enumerate(cols):
293
        col = cols[i] = as_Col(col)
294
        col.table = table
295
296 2401 aaronmk
def to_name_only_col(col, check_table=None):
297
    col = as_Col(col)
298 3020 aaronmk
    if not is_table_col(col): return col
299 2401 aaronmk
300
    if check_table != None:
301
        table = col.table
302
        assert table == None or table == check_table
303
    return Col(col.name)
304
305 2993 aaronmk
def suffixed_col(col, suffix):
306
    return Col(concat(col.name, suffix), col.table, col.srcs)
307
308 2323 aaronmk
class NamedCol(Col):
309 2229 aaronmk
    def __init__(self, name, code):
310 2310 aaronmk
        Col.__init__(self, name)
311
312 3016 aaronmk
        code = as_Value(code)
313 2229 aaronmk
314
        self.code = code
315
316
    def to_str(self, db):
317 2310 aaronmk
        return self.code.to_str(db)+' AS '+Col.to_str(self, db)
318 2314 aaronmk
319
    def to_Col(self): return Col(self.name)
320 2229 aaronmk
321 2462 aaronmk
def remove_col_rename(col):
322
    if isinstance(col, NamedCol): col = col.code
323
    return col
324
325 2830 aaronmk
def underlying_col(col):
326
    col = remove_col_rename(col)
327 2849 aaronmk
    if not isinstance(col, Col): raise NoUnderlyingTableException
328
329 2902 aaronmk
    return Col(col.name, underlying_table(col.table), col.srcs)
330 2830 aaronmk
331 2703 aaronmk
def wrap(wrap_func, value):
332
    '''Wraps a value, propagating any column renaming to the returned value.'''
333
    if isinstance(value, NamedCol):
334
        return NamedCol(value.name, wrap_func(value.code))
335
    else: return wrap_func(value)
336
337 2667 aaronmk
class ColDict(dicts.DictProxy):
338 2564 aaronmk
    '''A dict that automatically makes inserted entries Col objects'''
339
340 2645 aaronmk
    def __init__(self, db, keys_table, dict_={}):
341 2667 aaronmk
        dicts.DictProxy.__init__(self, {})
342
343 2645 aaronmk
        keys_table = as_Table(keys_table)
344
345 2642 aaronmk
        self.db = db
346 2641 aaronmk
        self.table = keys_table
347 2653 aaronmk
        self.update(dict_) # after setting vars because __setitem__() needs them
348 2641 aaronmk
349 2667 aaronmk
    def copy(self): return ColDict(self.db, self.table, self.inner.copy())
350 2655 aaronmk
351 2667 aaronmk
    def __getitem__(self, key):
352
        return dicts.DictProxy.__getitem__(self, self._key(key))
353 2653 aaronmk
354 2564 aaronmk
    def __setitem__(self, key, value):
355 2642 aaronmk
        key = self._key(key)
356 2819 aaronmk
        if value == None: value = self.db.col_info(key).default
357 2667 aaronmk
        dicts.DictProxy.__setitem__(self, key, as_Col(value, name=key.name))
358 2564 aaronmk
359 2641 aaronmk
    def _key(self, key): return as_Col(key, self.table)
360 2564 aaronmk
361 2524 aaronmk
##### Functions
362
363 2912 aaronmk
Function = Table
364 2911 aaronmk
as_Function = as_Table
365
366 2691 aaronmk
class InternalFunction(CustomCode): pass
367
368 2941 aaronmk
class NamedArg(NamedCol):
369
    def __init__(self, name, value):
370
        NamedCol.__init__(self, name, value)
371
372
    def to_str(self, db):
373
        return Col.to_str(self, db)+' := '+self.code.to_str(db)
374
375 2524 aaronmk
class FunctionCall(Code):
376 2941 aaronmk
    def __init__(self, function, *args, **kw_args):
377 2524 aaronmk
        '''
378 2690 aaronmk
        @param args [Code|literal-value...] The function's arguments
379 2524 aaronmk
        '''
380 3016 aaronmk
        function = as_Function(function)
381 2941 aaronmk
        def filter_(arg): return remove_col_rename(as_Value(arg))
382
        args = map(filter_, args)
383
        args += [NamedArg(k, filter_(v)) for k, v in kw_args.iteritems()]
384 2524 aaronmk
385
        self.function = function
386
        self.args = args
387
388
    def to_str(self, db):
389
        args_str = ', '.join((v.to_str(db) for v in self.args))
390
        return self.function.to_str(db)+'('+args_str+')'
391
392 2533 aaronmk
def wrap_in_func(function, value):
393
    '''Wraps a value inside a function call.
394
    Propagates any column renaming to the returned value.
395
    '''
396 2703 aaronmk
    return wrap(lambda v: FunctionCall(function, v), value)
397 2533 aaronmk
398 2561 aaronmk
def unwrap_func_call(func_call, check_name=None):
399
    '''Unwraps any function call to its first argument.
400
    Also removes any column renaming.
401
    '''
402
    func_call = remove_col_rename(func_call)
403
    if not isinstance(func_call, FunctionCall): return func_call
404
405
    if check_name != None:
406
        name = func_call.function.name
407
        assert name == None or name == check_name
408
    return func_call.args[0]
409
410 2986 aaronmk
##### Casts
411
412
class Cast(FunctionCall):
413
    def __init__(self, type_, value):
414
        value = as_Value(value)
415
416
        self.type_ = type_
417
        self.value = value
418
419
    def to_str(self, db):
420
        return 'CAST('+self.value.to_str(db)+' AS '+self.type_+')'
421
422 2335 aaronmk
##### Conditions
423 2259 aaronmk
424 2398 aaronmk
class ColValueCond(Code):
425
    def __init__(self, col, value):
426
        value = as_ValueCond(value)
427
428
        self.col = col
429
        self.value = value
430
431
    def to_str(self, db): return self.value.to_str(db, self.col)
432
433 2577 aaronmk
def combine_conds(conds, keyword=None):
434
    '''
435
    @param keyword The keyword to add before the conditions, if any
436
    '''
437
    str_ = ''
438
    if keyword != None:
439
        if conds == []: whitespace = ''
440
        elif len(conds) == 1: whitespace = ' '
441
        else: whitespace = '\n'
442
        str_ += keyword+whitespace
443
444
    str_ += '\nAND '.join(conds)
445
    return str_
446
447 2398 aaronmk
##### Condition column comparisons
448
449 2514 aaronmk
class ValueCond(BasicObject):
450 2213 aaronmk
    def __init__(self, value):
451 2858 aaronmk
        value = remove_col_rename(as_Value(value))
452 2213 aaronmk
453
        self.value = value
454 2214 aaronmk
455 2216 aaronmk
    def to_str(self, db, left_value):
456 2214 aaronmk
        '''
457 2216 aaronmk
        @param left_value The Code object that the condition is being applied on
458 2214 aaronmk
        '''
459
        raise NotImplemented()
460 2228 aaronmk
461 2514 aaronmk
    def __repr__(self): return self.to_str(mockDb, '<left_value>')
462 2211 aaronmk
463
class CompareCond(ValueCond):
464
    def __init__(self, value, operator='='):
465 2222 aaronmk
        '''
466
        @param operator By default, compares NULL values literally. Use '~=' or
467
            '~!=' to pass NULLs through.
468
        '''
469 2211 aaronmk
        ValueCond.__init__(self, value)
470
        self.operator = operator
471
472 2216 aaronmk
    def to_str(self, db, left_value):
473 2858 aaronmk
        left_value = remove_col_rename(as_Col(left_value))
474 2216 aaronmk
475 2222 aaronmk
        right_value = self.value
476
477
        # Parse operator
478 2216 aaronmk
        operator = self.operator
479 2222 aaronmk
        passthru_null_ref = [False]
480
        operator = strings.remove_prefix('~', operator, passthru_null_ref)
481
        neg_ref = [False]
482
        operator = strings.remove_prefix('!', operator, neg_ref)
483 2844 aaronmk
        equals = operator.endswith('=') # also includes <=, >=
484 2222 aaronmk
485 2825 aaronmk
        # Handle nullable columns
486
        check_null = False
487 2844 aaronmk
        if not passthru_null_ref[0]: # NULLs compare equal
488 2857 aaronmk
            try: left_value = ensure_not_null(db, left_value)
489 2844 aaronmk
            except ensure_not_null_excs: # fall back to alternate method
490
                check_null = equals and isinstance(right_value, Col)
491 2837 aaronmk
            else:
492 2857 aaronmk
                if isinstance(left_value, EnsureNotNull):
493
                    right_value = ensure_not_null(db, right_value,
494
                        left_value.type) # apply same function to both sides
495 2825 aaronmk
496 2844 aaronmk
        if equals and is_null(right_value): operator = 'IS'
497
498 2825 aaronmk
        left = left_value.to_str(db)
499
        right = right_value.to_str(db)
500
501 2222 aaronmk
        # Create str
502
        str_ = left+' '+operator+' '+right
503 2825 aaronmk
        if check_null:
504 2578 aaronmk
            str_ = '('+str_+' OR ('+left+' IS NULL AND '+right+' IS NULL))'
505
        if neg_ref[0]: str_ = 'NOT '+str_
506 2222 aaronmk
        return str_
507 2216 aaronmk
508 2260 aaronmk
# Tells as_ValueCond() to assume a non-ValueCond is a literal value
509
assume_literal = object()
510
511
def as_ValueCond(value, default_table=assume_literal):
512
    if not isinstance(value, ValueCond):
513
        if default_table is not assume_literal:
514 2748 aaronmk
            value = with_default_table(value, default_table)
515 2260 aaronmk
        return CompareCond(value)
516 2216 aaronmk
    else: return value
517 2219 aaronmk
518 2335 aaronmk
##### Joins
519
520 2352 aaronmk
join_same = object() # tells Join the left and right columns have the same name
521 2260 aaronmk
522 2353 aaronmk
# Tells Join the left and right columns have the same name and are never NULL
523
join_same_not_null = object()
524
525 2260 aaronmk
filter_out = object() # tells Join to filter out rows that match the join
526
527 2514 aaronmk
class Join(BasicObject):
528 2746 aaronmk
    def __init__(self, table, mapping={}, type_=None):
529 2260 aaronmk
        '''
530
        @param mapping dict(right_table_col=left_table_col, ...)
531 2352 aaronmk
            * if left_table_col is join_same: left_table_col = right_table_col
532 2353 aaronmk
              * Note that right_table_col must be a string
533
            * if left_table_col is join_same_not_null:
534
              left_table_col = right_table_col and both have NOT NULL constraint
535
              * Note that right_table_col must be a string
536 2260 aaronmk
        @param type_ None (for plain join)|str (e.g. 'LEFT')|filter_out
537
            * filter_out: equivalent to 'LEFT' with the query filtered by
538
              `table_pkey IS NULL` (indicating no match)
539
        '''
540
        if util.is_str(table): table = Table(table)
541
        assert type_ == None or util.is_str(type_) or type_ is filter_out
542
543
        self.table = table
544
        self.mapping = mapping
545
        self.type_ = type_
546
547 2749 aaronmk
    def to_str(self, db, left_table_):
548 2260 aaronmk
        def join(entry):
549
            '''Parses non-USING joins'''
550
            right_table_col, left_table_col = entry
551
552 2353 aaronmk
            # Switch order (right_table_col is on the left in the comparison)
553
            left = right_table_col
554
            right = left_table_col
555 2749 aaronmk
            left_table = self.table
556
            right_table = left_table_
557 2353 aaronmk
558 2747 aaronmk
            # Parse left side
559 2748 aaronmk
            left = with_default_table(left, left_table)
560 2747 aaronmk
561 2260 aaronmk
            # Parse special values
562 2747 aaronmk
            left_on_right = Col(left.name, right_table)
563
            if right is join_same: right = left_on_right
564 2353 aaronmk
            elif right is join_same_not_null:
565 2747 aaronmk
                right = CompareCond(left_on_right, '~=')
566 2260 aaronmk
567 2747 aaronmk
            # Parse right side
568 2353 aaronmk
            right = as_ValueCond(right, right_table)
569 2747 aaronmk
570
            return right.to_str(db, left)
571 2260 aaronmk
572 2265 aaronmk
        # Create join condition
573
        type_ = self.type_
574 2276 aaronmk
        joins = self.mapping
575 2746 aaronmk
        if joins == {}: join_cond = None
576
        elif type_ is not filter_out and reduce(operator.and_,
577 2460 aaronmk
            (v is join_same_not_null for v in joins.itervalues())):
578 2260 aaronmk
            # all cols w/ USING, so can use simpler USING syntax
579 2747 aaronmk
            cols = map(to_name_only_col, joins.iterkeys())
580
            join_cond = 'USING ('+(', '.join((c.to_str(db) for c in cols)))+')'
581 2757 aaronmk
        else: join_cond = combine_conds(map(join, joins.iteritems()), 'ON')
582 2260 aaronmk
583 2757 aaronmk
        if isinstance(self.table, NamedTable): whitespace = '\n'
584
        else: whitespace = ' '
585
586 2260 aaronmk
        # Create join
587
        if type_ is filter_out: type_ = 'LEFT'
588 2266 aaronmk
        str_ = ''
589
        if type_ != None: str_ += type_+' '
590 2757 aaronmk
        str_ += 'JOIN'+whitespace+self.table.to_str(db)
591
        if join_cond != None: str_ += whitespace+join_cond
592 2266 aaronmk
        return str_
593 2349 aaronmk
594 2514 aaronmk
    def __repr__(self): return self.to_str(mockDb, '<left_table>')
595 2424 aaronmk
596
##### Value exprs
597
598 3089 aaronmk
all_cols = CustomCode('*')
599
600 2737 aaronmk
default = CustomCode('DEFAULT')
601
602 3090 aaronmk
row_count = FunctionCall(InternalFunction('COUNT'), all_cols)
603 2674 aaronmk
604 3061 aaronmk
class Coalesce(FunctionCall):
605
    def __init__(self, *args):
606
        FunctionCall.__init__(self, InternalFunction('COALESCE'), *args)
607 3060 aaronmk
608 3062 aaronmk
class Nullif(FunctionCall):
609
    def __init__(self, *args):
610
        FunctionCall.__init__(self, InternalFunction('NULLIF'), *args)
611
612 2850 aaronmk
# See <http://www.postgresql.org/docs/8.3/static/datatype-numeric.html>
613 2958 aaronmk
null_sentinels = {
614
    'character varying': r'\N',
615
    'double precision': 'NaN',
616
    'integer': 2147483647,
617
    'text': r'\N',
618
    'timestamp with time zone': 'infinity'
619
}
620 2692 aaronmk
621 3061 aaronmk
class EnsureNotNull(Coalesce):
622 2850 aaronmk
    def __init__(self, value, type_):
623 3061 aaronmk
        Coalesce.__init__(self, as_Col(value),
624 2988 aaronmk
            Cast(type_, null_sentinels[type_]))
625 2850 aaronmk
626
        self.type = type_
627 3001 aaronmk
628
    def to_str(self, db):
629
        col = self.args[0]
630
        index_col_ = index_col(col)
631
        if index_col_ != None: return index_col_.to_str(db)
632 3061 aaronmk
        return Coalesce.to_str(self, db)
633 2850 aaronmk
634 2737 aaronmk
##### Table exprs
635
636
class Values(Code):
637
    def __init__(self, values):
638 2739 aaronmk
        '''
639
        @param values [...]|[[...], ...] Can be one or multiple rows.
640
        '''
641
        rows = values
642
        if len(values) >= 1 and not lists.is_seq(values[0]): # only one row
643
            rows = [values]
644
        for i, row in enumerate(rows):
645
            rows[i] = map(remove_col_rename, map(as_Value, row))
646 2737 aaronmk
647 2739 aaronmk
        self.rows = rows
648 2737 aaronmk
649
    def to_str(self, db):
650 2739 aaronmk
        def row_str(row):
651
            return '('+(', '.join((v.to_str(db) for v in row)))+')'
652
        return 'VALUES '+(', '.join(map(row_str, self.rows)))
653 2737 aaronmk
654 2740 aaronmk
def NamedValues(name, cols, values):
655 2745 aaronmk
    '''
656 3048 aaronmk
    @param cols None|[...]
657 2745 aaronmk
    @post `cols` will be changed to Col objects with the table set to `name`.
658
    '''
659 2834 aaronmk
    table = NamedTable(name, Values(values), cols)
660 3048 aaronmk
    if cols != None: set_cols_table(table, cols)
661 2834 aaronmk
    return table
662 2740 aaronmk
663 2674 aaronmk
##### Database structure
664
665
class TypedCol(Col):
666 2871 aaronmk
    def __init__(self, name, type_, default=None, nullable=True,
667
        constraints=None):
668 2818 aaronmk
        assert default == None or isinstance(default, Code)
669
670 2674 aaronmk
        Col.__init__(self, name)
671
672
        self.type = type_
673 2818 aaronmk
        self.default = default
674
        self.nullable = nullable
675 2871 aaronmk
        self.constraints = constraints
676 2674 aaronmk
677 2818 aaronmk
    def to_str(self, db):
678
        str_ = Col.to_str(self, db)+' '+self.type
679
        if not self.nullable: str_ += ' NOT NULL'
680
        if self.default != None: str_ += ' DEFAULT '+self.default.to_str(db)
681 2871 aaronmk
        if self.constraints != None: str_ += ' '+self.constraints
682 2818 aaronmk
        return str_
683 2674 aaronmk
684
    def to_Col(self): return Col(self.name)
685 2822 aaronmk
686 2840 aaronmk
ensure_not_null_excs = (NoUnderlyingTableException, KeyError)
687
688 2851 aaronmk
def ensure_not_null(db, col, type_=None):
689 2840 aaronmk
    '''
690 2855 aaronmk
    @param col If type_ is not set, must have an underlying column.
691 2851 aaronmk
    @param type_ If set, overrides the underlying column's type.
692 2840 aaronmk
    @return EnsureNotNull|Col
693
    @throws ensure_not_null_excs
694
    '''
695 2855 aaronmk
    nullable = True
696
    try: typed_col = db.col_info(underlying_col(col))
697
    except NoUnderlyingTableException:
698
        if type_ == None: raise
699
    else:
700
        if type_ == None: type_ = typed_col.type
701
        nullable = typed_col.nullable
702
703 2953 aaronmk
    if nullable:
704
        try: col = EnsureNotNull(col, type_)
705
        except KeyError, e:
706
            # Warn of no null sentinel for type, even if caller catches error
707
            warnings.warn(UserWarning(exc.str_(e)))
708
            raise
709
710 2840 aaronmk
    return col