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