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