Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

root/trunk/geniusql/objects.py

Revision 310 (checked in by lakin, 4 months ago)

geniusql - fixing whitespcae

  • Property svn:eol-style set to native
Line 
1 """Geniusql architectural classes.
2
3 The Column and Index classes model corresponding database objects, and are
4 intentionally simple. They should rarely contain any SQL or "smarts" of
5 any kind, besides the "qname", the quoted name, of the column or index.
6 At most, subclasses and consumers might put implementation-specific data
7 into them.
8
9 The IndexSet, Table, and Schema objects are all dict-like containers,
10 and therefore have a key for each value. Those keys should equate to things
11 at the consumer layer; for example, a Schema may possess a pair of the
12 form {'YoYo': Table('yoyo')} -- the key is the "friendly" name, but the
13 Table.name is a lowercase version of that, because that's what the database
14 uses in SQL to refer to that table.
15 """
16
17 import threading
18
19 import geniusql
20 from geniusql import errors, typerefs
21 from geniusql import dbtypes
22 from geniusql import conns
23 from geniusql import deparse
24 from geniusql import isolation
25 from geniusql import logic
26 from geniusql import sqlwriters
27
28 __all__ = [
29     'Index', 'IndexSet', 'Column', 'Table', 'View', 'Schema', 'Database', 'Dataset',
30     ]
31
32 class Bijection(dict):
33     """Bijective dict. Each key maps to only one value (and vice-versa)."""
34
35     def key_for(self, obj):
36         """For the given value, return its corresponding key."""
37         for key, val in self.iteritems():
38             if val is obj:
39                 return key
40         raise ValueError("The given object could not be found: %r" % obj)
41
42     def alias(self, oldname, newname):
43         """Move the object at the given, existing key to the new key.
44
45         Consumer code should call this method when user-supplied object
46         names do not match the names in the database.
47         """
48         if oldname == newname:
49             return
50
51         obj = self[oldname]
52         if newname in self:
53             dict.__delitem__(self, newname)
54         dict.__delitem__(self, oldname)
55         dict.__setitem__(self, newname, obj)
56
57
58 class Index(object):
59     """An index on a table column (or columns) in a database."""
60
61     def __init__(self, name, qname, tablename, colname, unique=True):
62         self.name = name
63         self.qname = qname
64         self.tablename = tablename
65         self.colname = colname
66         self.unique = unique
67
68     def __repr__(self):
69         return ("%s.%s(%r, %r, %r, %r, unique=%r)" %
70                 (self.__module__, self.__class__.__name__,
71                  self.name, self.qname, self.tablename,
72                  self.colname, self.unique))
73
74     def __copy__(self):
75         return self.__class__(self.name, self.qname, self.tablename,
76                               self.colname, self.unique)
77     copy = __copy__
78
79
80 class IndexSet(Bijection):
81
82     def __new__(cls, table):
83         return dict.__new__(cls)
84
85     def __init__(self, table):
86         dict.__init__(self)
87         self.table = table
88
89     def __getstate__(self):
90         return self.items()
91
92     def __setstate__(self, state):
93         self.update(state)
94
95     def __setitem__(self, key, index):
96         """Create the specified index."""
97         t = self.table
98         if t.created:
99             t.schema.db.execute_ddl('CREATE INDEX %s ON %s (%s);' %
100                                     (index.qname, t.qname,
101                                      t.schema.db.quote(index.colname)))
102         dict.__setitem__(self, key, index)
103
104     def __delitem__(self, key):
105         """Drop the specified index."""
106         t = self.table
107         if t.created:
108             t.schema.db.execute_ddl('DROP INDEX %s ON %s;' %
109                                     (self[key].qname, t.qname))
110         dict.__delitem__(self, key)
111
112
113 class Column(object):
114     """A column in a table or view in a database.
115
116     name: the SQL name for this table (unquoted).
117     qname: the SQL name for this table (quoted).
118
119     pytype: the Python type (the actual type object, not its name).
120     dbtype: a DatabaseType instance.
121     adapter: the object whose push and pull methods will convert Python
122         values to and from SQL for values in this Column.
123
124     default: default Python value for this column for new rows.
125     key: True if this column is part of the table's primary key.
126
127     autoincrement: if True, uses the database's built-in sequencing.
128     sequence_name: for databases that use separate statements to create and
129         drop sequences, this stores the name of the sequence.
130     initial: if autoincrement, holds the initial value for the sequence.
131     """
132
133     def __init__(self, pytype, dbtype, default=None, key=False,
134                  name=None, qname=None):
135         self.pytype = pytype
136         self.dbtype = dbtype
137         self.adapter = None
138
139         self.name = name
140         self.qname = qname
141         self.default = default
142         self.key = key
143
144         # If autoincrement, the initial value should be put in self.initial.
145         self.autoincrement = False
146         self.sequence_name = None
147         self.initial = 1
148
149     def __repr__(self):
150         return ("%s.%s(%r, %r, default=%r, key=%r, name=%r, qname=%r)" %
151                 (self.__module__, self.__class__.__name__,
152                  self.pytype, self.dbtype, self.default, self.key,
153                  self.name, self.qname)
154                 )
155
156     def __copy__(self):
157         newcol = self.__class__(self.pytype, self.dbtype.copy(),
158                                 self.default, self.key,
159                                 self.name, self.qname)
160         newcol.autoincrement = self.autoincrement
161         newcol.initial = self.initial
162         newcol.adapter = self.adapter
163         return newcol
164     copy = __copy__
165
166
167 class Table(Bijection):
168     """A table in a database; a dict of Column objects.
169
170     Values in this dict must be instances of Column (or a subclass of it).
171     Keys should be consumer-friendly names for each Column value.
172
173     name: the SQL name for this table (unquoted).
174     qname: the SQL name for this table (quoted).
175     schema: the schema for this table.
176     created: whether or not this Table has a concrete implementation in the
177         database. If False (the default), then changes to Table items can be
178         made with impunity. If True, then appropriate ALTER TABLE commands
179         are executed whenever a consumer adds or deletes items from the
180         Table, or calls methods like 'rename'.
181     indices: a dict-like IndexSet of Index objects.
182     references: a dict of the form:
183         {name: (near Column key, far Table key, far Column key)}.
184     """
185
186     implicit_pkey_indices = False
187
188     def __new__(cls, name, qname, schema, created=False, description=None):
189         return dict.__new__(cls)
190
191     def __init__(self, name, qname, schema, created=False, description=None):
192         dict.__init__(self)
193
194         self.name = name
195         self.qname = qname
196         self.schema = schema
197         self.created = created
198         self.description = description
199
200         self.indices = schema.indexsetclass(self)
201         self.references = {}
202
203     def __repr__(self):
204         name = getattr(self, "name", "<unknown>")
205         qname = getattr(self, "qname", "<unknown>")
206         return ("%s.%s(%r, %r)" %
207                 (self.__module__, self.__class__.__name__, name, qname))
208
209     def __copy__(self):
210         # Don't set 'created' when copying!
211         newtable = self.__class__(self.name, self.qname, self.schema)
212         for key, c in self.iteritems():
213             dict.__setitem__(newtable, key, c.copy())
214         for key, i in self.indices.iteritems():
215             dict.__setitem__(newtable.indices, key, i.copy())
216         return newtable
217     copy = __copy__
218
219     def __getstate__(self):
220         return (self.name, self.qname, self.items(), self.indices)
221
222     def __setstate__(self, state):
223         self.name, self.qname, items, self.indices = state
224         self.update(items)
225         self.indices.table = self
226
227     def create(self):
228         """Create this table in the database."""
229         fields = []
230         pk, pkindices = [], []
231         for key, column in self.iteritems():
232             if column.autoincrement:
233                 # Columns created using schema.column() can't make their own
234                 # sequence names because the tablename isn't available.
235                 # So we do it here if needed.
236                 if column.sequence_name is None:
237                     column.sequence_name = self.schema.sequence_name(self.name, key)
238                 # This may or may not be a no-op, depending on the DB.
239                 self.schema.create_sequence(self, column)
240
241             fields.append(self.schema.columnclause(column))
242             if column.key:
243                 pk.append(column.qname)
244                 if self.implicit_pkey_indices:
245                     pkindices.append(column.name)
246
247         if pk:
248             pk = ", PRIMARY KEY (%s)" % ", ".join(pk)
249         else:
250             pk = ""
251
252         # Just to be sure...
253         try:
254             self.schema.db.execute_ddl('DROP TABLE %s;' % self.qname)
255         except:
256             pass
257         self.schema.db.execute_ddl('CREATE TABLE %s (%s%s);' %
258                                    (self.qname, ", ".join(fields), pk))
259         # Set table.created to True, which should "turn on"
260         # any future ALTER TABLE statements.
261         self.created = True
262
263         for index in self.indices.itervalues():
264             if index.colname in pkindices:
265                 # Skip this index since the database has already implicitly
266                 # created an index for the primary key.
267                 continue
268             self.schema.db.execute_ddl('CREATE INDEX %s ON %s (%s);' %
269                                        (index.qname, self.qname,
270                                         self.schema.db.quote(index.colname)))
271
272     def drop(self):
273         """Drop this table from the database."""
274         self.schema.db.execute_ddl('DROP TABLE %s;' % self.qname)
275         for col in self.itervalues():
276             if col.autoincrement:
277                 self.schema.drop_sequence(col)
278
279     # ------------------------ Column management ------------------------ #
280
281     def _add_column(self, column):
282         """Internal function to add the column to the database."""
283         coldef = self.schema.columnclause(column)
284         self.schema.db.execute("ALTER TABLE %s ADD COLUMN %s;" %
285                                (self.qname, coldef))
286
287     def __setitem__(self, key, column):
288         if column.name is None:
289             column.name = self.schema.column_name(self.name, key)
290             column.qname = self.schema.db.quote(column.name)
291
292         if not self.created:
293             dict.__setitem__(self, key, column)
294             return
295
296         if key in self:
297             del self[key]
298
299         if column.autoincrement:
300             # Columns created using schema.column() can't make their own
301             # sequence names because the tablename isn't available.
302             # So we do it here if needed.
303             if column.sequence_name is None:
304                 column.sequence_name = self.schema.sequence_name(self.name, key)
305             # This may or may not be a no-op, depending on the DB.
306             self.schema.create_sequence(self, column)
307         self._add_column(column)
308         dict.__setitem__(self, key, column)
309
310     def _drop_column(self, column):
311         """Internal function to drop the column from the database."""
312         self.schema.db.execute_ddl("ALTER TABLE %s DROP COLUMN %s;" %
313                                    (self.qname, column.qname))
314
315     def __delitem__(self, key):
316         if key in self.indices:
317             del self.indices[key]
318
319         if not self.created:
320             dict.__delitem__(self, key)
321             return
322
323         column = self[key]
324         self._drop_column(column)
325         if column.autoincrement:
326             # This may or may not be a no-op, depending on the DB.
327             self.schema.drop_sequence(column)
328         dict.__delitem__(self, key)
329
330     def _rename(self, oldcol, newcol):
331         # Override this to do the actual rename at the DB level.
332         self.schema.db.execute_ddl("ALTER TABLE %s RENAME COLUMN %s TO %s;" %
333                                    (self.qname, oldcol.qname, newcol.qname))
334
335     def rename(self, oldkey, newkey):
336         """Rename a Column. This will change the table name in the database."""
337         oldcol = self[oldkey]
338
339         if not self.created:
340             dict.__delitem__(self, oldkey)
341             dict.__setitem__(self, newkey, oldcol)
342             return
343
344         oldname = oldcol.name
345         newname = self.schema.column_name(self.name, newkey)
346
347         if oldname != newname:
348             newcol = oldcol.copy()
349             newcol.name = newname
350             newcol.qname = self.schema.db.quote(newname)
351             self._rename(oldcol, newcol)
352
353         # Use the superclass calls to avoid DROP COLUMN/ADD COLUMN.
354         dict.__delitem__(self, oldkey)
355         dict.__setitem__(self, newkey, newcol)
356
357     def add_index(self, columnkey):
358         """Add and return a new Index for the given column key.
359
360         The new Index object will possess the same key as the column.
361         The actual SQL name of the new Index will be determined by
362         Schema.index_name.
363         """
364         name = self.schema.index_name(self, columnkey)
365         i = Index(name, self.schema.db.quote(name), self.name,
366                   self[columnkey].name)
367         # This won't call CREATE INDEX if self.created is False.
368         self.indices[columnkey] = i
369         return i
370
371     def set_primary(self):
372         """Set the PRIMARY KEY for this Table, using its Column.key values.
373
374         If self already possesses a primary key, this method will DROP it.
375         This is intended to be used with install or repair scripts.
376         """
377         self.drop_primary()
378         pk = [column.qname for column in self.itervalues() if column.key]
379         if pk:
380             self.schema.db.execute("ALTER TABLE %s ADD PRIMARY KEY (%s);" %
381                                    (self.qname, ", ".join(pk)))
382
383     def drop_primary(self):
384         """Remove any PRIMARY KEY for this Table."""
385         raise NotImplementedError
386
387     # ---------------------------- OLTP/CRUD ---------------------------- #
388
389     def id_clause(self, **kwargs):
390         """Return an SQL expression for the identifiers of the given table."""
391         ids = dict([(k, v) for k, v in kwargs.iteritems() if self[k].key])
392         try:
393             return logic.filter(**ids)
394         except Exception, x:
395             x.args += (kwargs, )
396             raise
397
398     def insert(self, **kwargs):
399         """Insert a row and return it, including any new identifiers."""
400         idkeys = []
401         values = {}
402         for key, col in self.iteritems():
403             if col.autoincrement and kwargs.get(key) is None:
404                 # Skip this field, since we're using a sequencer
405                 idkeys.append(key)
406                 continue
407             if key in kwargs:
408                 values[key] = kwargs[key]
409
410         conn = self.schema.db.connections.get()
411         self.schema.db.insert((self, values), conn)
412
413         # Note that the 'kwargs' dict has already been copied simply
414         # by being passed as kwargs. So modifying it in-place won't
415         # mangle the caller's original dict.
416         if idkeys:
417             for k, v in self._grab_new_ids(idkeys, conn).iteritems():
418                 col = self[k]
419                 kwargs[k] = col.adapter.pull(v, col.dbtype)
420         return kwargs
421
422     def _grab_new_ids(self, idkeys, conn):
423         # Override this to fetch and return new autoincrement values.
424         raise NotImplementedError
425
426     def save(self, **kwargs):
427         """Update a row (or rows) using the given identifiers in kwargs.
428
429         Any columns in the kwargs provided which are 'key' columns
430         will be used to determine which rows to update. All matching
431         rows will be updated using the other (non-key) columns.
432         Any autoincrement columns will be skipped.
433
434         Usually, kwarg values will be coerced (via column.adapter.push)
435         to the proper SQL for you. But for any kwarg value that is a lambda
436         or logic.Expression, the SQL will be deparsed from that value.
437         For example, to do full-text searching in PostgreSQL using Tsearch2,
438         you can write:
439
440             table.save(ID=3, fti=lambda t: to_tsvector('default', t.title))
441
442         which (assuming you have registered the to_tsvector function in
443         logic.builtins) will result in the SQL:
444
445             UPDATE table SET fti = to_tsvector('default', title) WHERE ID = 3
446         """
447         # Skip sequenced fields
448         parms = dict([(k, v) for k, v in kwargs.iteritems()
449                       if not self[k].autoincrement])
450         if parms:
451             w = self.id_clause(**kwargs)
452             self.schema.db.save((self, parms, w))
453
454     def save_all(self, data, restriction=None, **kwargs):
455         """Update all rows (with 'data' dict) which match the given restriction.
456
457         This differs from the 'save' method by allowing you to specify
458         matching rows using any restriction, not just primary key identities.
459         """
460         if data:
461             w = logic.combine(restriction, kwargs)
462             self.schema.db.save((self, data, w))
463
464     def delete(self, **kwargs):
465         """Delete all rows which match the given identifier kwargs."""
466         self.schema.db.delete((self, [], self.id_clause(**kwargs)))
467
468     def delete_all(self, restriction=None, **kwargs):
469         """Delete all rows which match the given restriction."""
470         self.schema.db.delete((self, [], logic.combine(restriction, kwargs)))
471
472     def select(self, restriction=None, **kwargs):
473         """Return a single data dict matching the given restriction (or None)."""
474         try:
475             return self._select_lazy(restriction, **kwargs).next()
476         except StopIteration:
477             return None
478
479     def select_all(self, restriction=None, order=None, limit=None, **kwargs):
480         """Return a list of all data dicts matching the given restriction."""
481         return list(self._select_lazy(restriction, order, limit, **kwargs))
482
483     def _select_lazy(self, restriction=None, order=None, limit=None, **kwargs):
484         """Yield data dicts matching the given restriction."""
485         restriction = logic.combine(restriction, kwargs)
486
487         attrs = self.keys()
488         dataset = self.schema.db.select((self, attrs, restriction),
489                                         order=order, limit=limit,
490                                         strict=False)
491         if dataset.statement.imperfect:
492             if restriction is None:
493                 raise ValueError("Could not generate perfect SQL.")
494
495             # Run a dummy object through our restriction before yielding.
496             # Since the results are imperfect (a larger subset of the data),
497             # we need to do our own limiting.
498             seen = 0
499             for row in dataset:
500                 row = dict(zip(attrs, row))
501                 if not restriction(_ImperfectDummy(**row)):
502                     continue
503                 yield row
504                 seen += 1
505                 if limit is not None and seen >= limit:
506                     return
507         else:
508             for row in dataset:
509                 yield dict(zip(attrs, row))
510
511
512     # ------------------------------ Joins ------------------------------ #
513
514     def __lshift__(self, other):
515         return geniusql.Join(self, other, leftbiased=True)
516     __rrshift__ = __lshift__
517
518     def __rshift__(self, other):
519         return geniusql.Join(self, other, leftbiased=False)
520     __rlshift__ = __rshift__
521
522     def __and__(self, other):
523         return geniusql.Join(self, other)
524
525     def __rand__(self, other):
526         return geniusql.Join(other, self)
527
528
529 class View(Bijection):
530     """A view in a database; a dict of Column objects for a query.
531
532     Values in this dict must be instances of Column (or a subclass of it).
533     Keys should be consumer-friendly names for each Column value.
534
535     name: the SQL name for this view (unquoted).
536     qname: the SQL name for this view (quoted).
537     schema: the schema for this view.
538     created: whether or not this View has a concrete implementation in the
539         database. If False (the default), then changes to View items can be
540         made with impunity. If True, then appropriate ALTER VIEW commands (or
541         the equivalent DROP and re-CREATE commands) are executed whenever a
542         consumer adds or deletes items from the View, or calls methods like
543         'rename'.
544     statement: a Statement instance. Setting this value automatically
545         populates self.items with Column objects based on the statement's
546         output.
547     """
548
549     def __new__(cls, name, qname, schema, created=False, description=None,
550                 statement=None):
551         return dict.__new__(cls)
552
553     def __init__(self, name, qname, schema, created=False, description=None,
554                  statement=None):
555         dict.__init__(self)
556
557         self.name = name
558         self.qname = qname
559         self.schema = schema
560         self.created = created
561         self.description = description
562         self.statement = statement
563
564     def __repr__(self):
565         name = getattr(self, "name", "<unknown>")
566         qname = getattr(self, "qname", "<unknown>")
567         return ("%s.%s(%r, %r)" %
568                 (self.__module__, self.__class__.__name__, name, qname))
569
570     def __copy__(self):
571         # Don't set 'created' when copying!
572         newview = self.__class__(self.name, self.qname, self.schema,
573                                  statement=self.statement)
574         for key, c in self.iteritems():
575             dict.__setitem__(newview, key, c.copy())
576         return newview
577     copy = __copy__
578
579     def __getstate__(self):
580         return (self.name, self.qname, self.items(), self.statement)
581
582     def __setstate__(self, state):
583         self.name, self.qname, items, self.statement = state
584         self.update(items)
585
586     def _get_statement(self):
587         return self._statement
588     def _set_statement(self, value):
589         self._statement = value
590         if value is None:
591             return
592
593         db = self.schema.db
594         self.sqlstatement = db.selectwriter(db, self._statement).statement
595         if self.sqlstatement.imperfect:
596             raise ValueError("The given restriction could not safely be "
597                              "translated to SQL.",
598                              self._statement.query.restriction)
599
600         # Make new Column objects for self, using the SELECT output names.
601         dict.clear(self)
602         for colkey, name, qname, col in self.sqlstatement.output:
603             col.name = name
604             col.qname = qname
605             col.key = False
606             col.autoincrement = False
607             col.sequence_name = None
608             col.initial = 1
609             dict.__setitem__(self, colkey, col)
610     statement = property(_get_statement, _set_statement,
611         doc="The Statement instance for the source of this View.")
612
613     def create(self):
614         """Create this view in the database."""
615         self.schema.db.execute_ddl('CREATE VIEW %s AS %s;' %
616                                    (self.qname, self.sqlstatement.sql))
617
618         # Set self.created to True, which should "turn on"
619         # any future ALTER VIEW statements.
620         self.created = True
621
622     def drop(self):
623         """Drop this view from the database."""
624         self.schema.db.execute_ddl('DROP VIEW %s;' % self.qname)
625
626     # ------------------------ Column management ------------------------ #
627
628     def __setitem__(self, key, column):
629         if column.name is None:
630             column.name = self.schema.column_name(self.name, key)
631             column.qname = self.schema.db.quote(column.name)
632
633         dict.__setitem__(self, key, column)
634         if self.created:
635             # DROP and CREATE (self.create auto-drops)
636             self.create()
637
638     def __delitem__(self, key):
639         dict.__delitem__(self, key)
640         if self.created:
641             # DROP and CREATE (self.create auto-drops)
642             self.create()
643
644     def rename(self, oldkey, newkey):
645         """Rename a Column. This will change the table name in the database."""
646         oldcol = self[oldkey]
647         oldname = oldcol.name
648         newname = self.schema.column_name(self.name, newkey)
649         if oldname != newname:
650             newcol = oldcol.copy()
651             newcol.name = newname
652             newcol.qname = self.schema.db.quote(newname)
653             if self.created:
654                 # DROP and CREATE (self.create auto-drops)
655                 self.create()
656
657         # Use the superclass calls to avoid further SQL.
658         dict.__delitem__(self, oldkey)
659         dict.__setitem__(self, newkey, newcol)
660
661     # ---------------------------- OLTP/CRUD ---------------------------- #
662
663     def select(self, restriction=None, **kwargs):
664         """Return a single data dict matching the given restriction (or None)."""
665         try:
666             return self._select_lazy(restriction, **kwargs).next()
667         except StopIteration:
668             return None
669
670     def select_all(self, restriction=None, order=None, limit=None, **kwargs):
671         """Return a list of all data dicts matching the given restriction."""
672         return list(self._select_lazy(restriction, order, limit, **kwargs))
673
674     def _select_lazy(self, restriction=None, order=None, limit=None, **kwargs):
675         """Yield data dicts matching the given restriction."""
676         restriction = logic.combine(restriction, kwargs)
677
678         attrs = self.keys()
679         dataset = self.schema.db.select((self, attrs, restriction),
680                                         order=order, limit=limit,
681                                         strict=False)
682         if dataset.statement.imperfect:
683             if restriction is None:
684                 raise ValueError("Could not generate perfect SQL.")
685
686             # Run a dummy object through our restriction before yielding.
687             # Since the results are imperfect (a larger subset of the data),
688             # we need to do our own limiting.
689             seen = 0
690             for row in dataset:
691                 row = dict(zip(attrs, row))
692                 if not restriction(_ImperfectDummy(**row)):
693                     continue
694                 yield row
695                 seen += 1
696                 if seen >= limit:
697                     return
698         else:
699             for row in dataset:
700                 yield dict(zip(attrs, row))
701
702
703     # ------------------------------ Joins ------------------------------ #
704
705     def __lshift__(self, other):
706         return geniusql.Join(self, other, leftbiased=True)
707     __rrshift__ = __lshift__
708
709     def __rshift__(self, other):
710         return geniusql.Join(self, other, leftbiased=False)
711     __rlshift__ = __rshift__
712
713     def __and__(self, other):
714         return geniusql.Join(self, other)
715
716     def __rand__(self, other):
717         return geniusql.Join(other, self)
718
719
720 class _ImperfectDummy(object):
721     """A dummy object for resolving imperfect queries."""
722     def __init__(self, **kwargs):
723         for k, v in kwargs.iteritems():
724             setattr(self, k, v)
725
726
727 class Schema(Bijection):
728     """A dict for managing a set of tables.
729
730     Values in this dict must be instances of Table. Keys should be
731     consumer-friendly names for each Table value. For example, it's
732     easiest to use all lowercase table names in MySQL; however, a
733     geniusql consumer might want their code to use TitledNames to
734     refer to each table.
735
736     This is a subclass of Bijection, so each key must map to one
737     and only one Table object (and vice-versa).
738
739     When a consumer adds and deletes items from a Schema object,
740     appropriate CREATE TABLE/DROP TABLE commands are executed.
741     This means that a Table object to be added should have all
742     of its columns populated before adding it to the Schema.
743     """
744
745     name = None
746     qname = None
747
748     tableclass = Table
749     viewclass = View
750     indexsetclass = IndexSet
751
752     def __new__(cls, db, name=None):
753         return dict.__new__(cls)
754
755     def __init__(self, db, name=None):
756         dict.__init__(self)
757
758         self.db = db
759         # Although __init__ should set this instance's "name" attribute,
760         # allow subclasses to pull that information from other sources
761         # if necessary.
762         if name is not None:
763             self.name = self.db.sql_name(name)
764             self.qname = self.db.quote(name)
765         self._discover_lock = threading.Lock()
766
767     def __repr__(self):
768         name = getattr(self, "name", "<unknown>")
769         return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, name)
770
771     #                              Discovery                              #
772
773     def _get_tables(self, conn=None):
774         raise NotImplementedError
775
776     def _get_table(self, tablename, conn=None):
777         # Fallback behavior. This is slow and should be optimized by each DB.
778         for t in self._get_tables(conn):
779             if t.name == tablename:
780                 return t
781         raise errors.MappingError("Table % not found." % tablename)
782
783     def _get_columns(self, table, conn=None):
784         raise NotImplementedError
785
786     def _get_indices(self, table, conn=None):
787         raise NotImplementedError
788
789     def _discover_table(self, table, conn=None):
790         """Populate the columns and indices of the given Table object."""
791         for col in self._get_columns(table, conn):
792             # Use the superclass call to avoid ALTER TABLE
793             if col.name in table:
794                 dict.__delitem__(table, col.name)
795             dict.__setitem__(table, col.name, col)
796
797         if hasattr(table, "indices"):
798             for idx in self._get_indices(table, conn):
799                 # Use the superclass call to avoid CREATE INDEX
800                 if idx.name in table.indices:
801                     dict.__delitem__(table.indices, idx.name)
802                 dict.__setitem__(table.indices, idx.name, idx)
803
804     def discover(self, tablename, conn=None):
805         """Attach a new Table or View from the underlying DB to self (and return it).
806
807         tablename: the database's name for the table. This may be different
808         from the schema's key for the table.
809
810         Table/View objects (and their Column and Index subobjects) will be
811         added to self using keys that match the database's names.
812         Consumers should call the "alias(oldname, newname)" method
813         of Schema, Table, and IndexSet in order to re-map the
814         discovered objects using consumer-friendly names.
815
816         If no such table/view exists, a MappingError should be raised.
817         """
818         self._discover_lock.acquire()
819         try:
820             table = self._get_table(tablename)
821             self._discover_table(table, conn)
822
823             # Use the superclass calls to avoid CREATE TABLE
824             if table.name in self:
825                 dict.__delitem__(self, table.name)
826             dict.__setitem__(self, table.name, table)
827
828             return table
829         finally:
830             self._discover_lock.release()
831
832     def discover_all(self, ignore=None, conn=None):
833         """(Re-)populate self (all table items) from the underlying DB.
834
835         ignore: a list of table names to ignore (e.g., system tables) or None.
836
837         Table objects (and their Column and Index subobjects) will be
838         added to self using keys that match the database's names.
839         Consumers should call the "alias(oldname, newname)" method
840         of Schema, Table, and IndexSet in order to re-map the
841         discovered objects using consumer-friendly names.
842
843         This method is idempotent, but that doesn't mean cheap. Try not
844         to call it very often (once at app startup is usually enough).
845         If you already know the names of all the tables you want to
846         discover, it's often faster to skip this method and just use
847         the discover(tablename) method for each known name instead.
848         """
849         ignore = ignore or []
850
851         self._discover_lock.acquire()
852         try:
853             for table in self._get_tables(conn):
854                 if table.name in ignore:
855                     continue
856                 self._discover_table(table, conn)
857
858                 # Use the superclass calls to avoid CREATE TABLE
859                 if table.name in self:
860                     dict.__delitem__(self, table.name)
861                 dict.__setitem__(self, table.name, table)
862         finally:
863             self._discover_lock.release()
864
865     def column_name(self, tablename, columnkey):
866         "Return the SQL column name for the given table name and column key."
867         # If you want to use a map from your ORM's property names
868         # to DB column names, override this method (that's why
869         # the tablename must be included in the args).
870         return self.db.sql_name(columnkey)
871
872     def sequence_name(self, tablename, columnkey):
873         "Return the SQL sequence name for the given table name and column key."
874         # If you want to use a map from your ORM's property names
875         # to DB sequence names, override this method (that's why
876         # the tablename must be included in the args).
877         return None
878
879     def index_name(self, table, columnkey):
880         "Return the SQL index name for the given table and column key."
881         return self.db.sql_name("i_%s_%s" % (table.name, table[columnkey].name))
882
883     def column(self, pytype=unicode, dbtype=None, default=None,
884                key=False, autoincrement=False, hints=None):
885         """Return a Column object from the given arguments."""
886         col = Column(pytype, dbtype, default, key)
887         col.autoincrement = autoincrement
888
889         if col.dbtype is None:
890             col.dbtype = self.db.typeset.database_type(pytype, hints or {})
891         col.adapter = col.dbtype.default_adapter(pytype)
892
893         return col
894
895     prefix = ""
896
897     def table_name(self, key):
898         """Return the SQL table name for the given key."""
899         # If you want to use a map from your ORM's class names
900         # to DB table names, override this method.
901         return self.db.sql_name(self.prefix + key)
902
903     def table(self, name):
904         """Create and return a Table object for the given name."""
905         name = self.table_name(name)
906         return self.tableclass(name, self.db.quote(name), self)
907
908     def view(self, name, statement):
909         """Create and return a View object for the given name and statement."""
910         name = self.table_name(name)
911         return self.viewclass(name, self.db.quote(name), schema=self,
912                               statement=statement)
913
914     def create_sequence(self, table, column):
915         """Create a SEQUENCE for the given column."""
916         # By default, this does nothing. Databases which require a separate
917         # statement to create a sequence generator should override this.
918         pass
919
920     def drop_sequence(self, column):
921         """Drop a SEQUENCE for the given column."""
922         # By default, this does nothing. Databases which require a separate
923         # statement to drop a sequence generator should override this.
924         pass
925
926     def columnclause(self, column):
927         """Return a clause for the given column for CREATE or ALTER TABLE.
928
929         This will be of the form "name type [DEFAULT x]".
930
931         Most subclasses will override this to add autoincrement support.
932         """
933         ddltype = column.dbtype.ddl()
934
935         default = column.default or ""
936         if default:
937             default = column.adapter.push(default, column.dbtype)
938             default = " DEFAULT %s" % default
939
940         return "%s %s%s" % (column.qname, ddltype, default)
941
942     def __setitem__(self, key, table):
943         if key in self:
944             del self[key]
945         table.create()
946         dict.__setitem__(self, key, table)
947
948     def __delitem__(self, key):
949         table = self[key]
950         table.drop()
951         dict.__delitem__(self, key)
952
953     def _rename(self, oldtable, newtable):
954         # Override this to do the actual rename at the DB level.
955         raise NotImplementedError
956         newtable.created = True
957
958     def rename(self, oldkey, newkey):
959         """Rename a Table."""
960         oldtable = self[oldkey]
961         oldname = oldtable.name
962         newname = self.db.table_name(newkey)
963
964         if oldname != newname:
965             newtable = oldtable.copy()
966             newtable.schema = self.schema
967             newtable.name = newname
968             newtable.qname = self.db.quote(newname)
969             self._rename(oldtable, newname)
970
971         # Use the superclass calls to avoid DROP TABLE/CREATE TABLE.
972         dict.__delitem__(self, oldkey)
973         dict.__setitem__(self, newkey, newtable)
974
975     def create(self):
976         """Create this schema in the database."""
977         self.clear()
978
979     def drop(self):
980         """Drop this schema (and any contained objects) from the database."""
981         # Must shut down all connections to avoid
982         # "being accessed by other users" error.
983         self.db.connections.shutdown()
984
985         seen = {}
986
987         # DROP views first to avoid dependency issues.
988         views = [(k, v) for k, v in self.items() if isinstance(v, View)]
989         for key, view in views:
990             # We might have multiple keys pointing at the same table.
991             if view.name in seen:
992                 dict.__delitem__(self, key)
993             else:
994                 del self[key]
995                 seen[view.name] = None
996
997         # Now drop remaining objects (tables).
998         for key, table in self.items():
999             # We might have multiple keys pointing at the same table.
1000             if table.name in seen:
1001                 dict.__delitem__(self, key)
1002             else:
1003                 del self[key]
1004                 seen[table.name] = None
1005
1006
1007 class Database(object):
1008
1009     __metaclass__ = geniusql._AttributeDocstrings
1010
1011     name = None
1012     qname = None
1013
1014     typeset = dbtypes.DatabaseTypeSet()
1015     deparser = deparse.SQLDeparser
1016     joinwrapper = sqlwriters.TableWrapper
1017     selectwriter = sqlwriters.SelectWriter
1018     updatewriter = sqlwriters.UpdateWriter
1019     deletewriter = sqlwriters.DeleteWriter
1020     insertwriter = sqlwriters.InsertWriter
1021     connectionmanager = conns.ConnectionManager
1022     schemaclass = Schema
1023
1024     multischema = True
1025     multischema__doc = """If True, instances of this Database class
1026     may spawn multiple Schema instances. This is False, for example,
1027     when the underlying engine binds connections to individual files.
1028     In most applications (that use a single schema) this presents no
1029     problems; applications that need to handle more than one schema
1030     at a time should inspect this value to determine whether they
1031     need a separate Database instance per Schema instance.
1032     """
1033
1034     pks_must_be_indexed = True
1035     pks_must_be_indexed__doc = """If True, the underlying database
1036     implements primary keys by creating an index. Geniusql allows
1037     you to define pk's without indexes, but not all databases do.
1038     """
1039
1040     ordered_views = True
1041     ordered_views__doc = """If True, the underlying database allows
1042     VIEWs to contain an ORDER BY clause. If False, ordering will
1043     have to be provided when selecting from the view rather than
1044     inside the view itself."""
1045
1046     def __init__(self, **kwargs):
1047         # Although __init__ should set this instance's "name" attribute,
1048         # allow subclasses to pull that information from other kwargs
1049         # or other sources if necessary.
1050         self.name = kwargs.get('name', None)
1051         if self.name:
1052             self.qname = self.quote(self.name)
1053
1054         self.connections = self.connectionmanager(self)
1055
1056         # Override attributes on self and children using kwargs.
1057         children = []
1058         for k, v in kwargs.iteritems():
1059             if "." in k:
1060                 # Defer subkeys in case the child is replaced wholesale
1061                 children.append((k, v))
1062             else:
1063                 setattr(self, k, v)
1064
1065         for k, v in children:
1066             namespace, name = k.split(".", 1)
1067             childobj = getattr(self, namespace)
1068             setattr(childobj, name, v)
1069
1070     def version(self):
1071         """Return a string containing version info for this database."""
1072         raise NotImplementedError
1073
1074     def log(self, msg):
1075         pass
1076
1077     def schema(self, name=None):
1078         return self.schemaclass(self, name)
1079
1080     def create(self):
1081         """Create this database."""
1082         self.execute_ddl("CREATE DATABASE %s;" % self.qname)
1083
1084     def exists(self):
1085         """Return True if this database has been created, False otherwise."""
1086         raise NotImplementedError()
1087
1088     def drop(self):
1089         """Drop this database."""
1090         # Must shut down all connections to avoid
1091         # "being accessed by other users" error.
1092         self.connections.shutdown()
1093         self.execute_ddl("DROP DATABASE %s;" % self.qname)
1094
1095     #                              Discovery                              #
1096
1097     def _get_dbinfo(self, conn=None):
1098         return {}
1099
1100     def discover_dbinfo(self, conn=None):
1101         """Set attributes on self with actual DB metadata, where possible."""
1102         for k, v in self._get_dbinfo(conn).iteritems():
1103             setattr(self, k, v)
1104
1105     def _get_schemas(self, conn=None):
1106         """Return a list of schema names."""
1107         raise NotImplementedError
1108
1109     def discover_schemas(self, conn=None):
1110         """Return a list of Schema objects."""
1111         if self.multischema:
1112             return [self.schema(name) for name in self._get_schemas(conn)]
1113         else:
1114             return self.schema()
1115
1116     #                               Naming                               #
1117
1118     sql_name_max_length = 64
1119     sql_name_caseless = False
1120
1121     def quote(self, name):
1122         """Return name, quoted for use in an SQL statement."""
1123         # This base class doesn't use "quote",
1124         # but most subclasses will.
1125         return name
1126
1127     def sql_name(self, key):
1128         """Return the native SQL version of key (unquoted)."""
1129         if self.sql_name_caseless:
1130             key = key.lower()
1131
1132         maxlen = self.sql_name_max_length
1133         if maxlen and len(key) > maxlen:
1134             errors.warn("The name '%s' is longer than the maximum of "
1135                         "%s characters." % (key, maxlen))
1136             key = key[:maxlen]
1137
1138         return key
1139
1140     def is_timeout_error(self, exc):
1141         """If the given exception instance is a lock timeout, return True.
1142
1143         This should return True for errors which arise from locking
1144         timeouts; for example, if the database prevents 'dirty reads'
1145         by raising an error.
1146         """
1147         # You should definitely override this for your database.
1148         return False
1149
1150     def is_connection_error(self, exc):
1151         """If the given exception instance is a connection error, return True.
1152
1153         This should return True for errors which arise from broken connections;
1154         for example, if the database server has dropped the connection socket,
1155         or is unreachable.
1156         """
1157         # You should definitely override this for your database.
1158         return False
1159
1160     def execute(self, sql, conn=None):
1161         """Return a native response for the given SQL."""
1162         if isinstance(sql, unicode):
1163             sql = sql.encode(self.typeset.encoding)
1164         self.log(sql)
1165
1166         if conn is None:
1167             conn = self.connections.get()
1168
1169         try:
1170             return conn.query(sql)
1171         except Exception, x:
1172             if self.is_connection_error(x):
1173                 self.connections.reset(conn)
1174                 return conn.query(sql)
1175             raise
1176
1177     def execute_ddl(self, sql, conn=None):
1178         """Return a native response for the given DDL statement.
1179
1180         In general, DDL statements should lock out other statements
1181         (especially those isolated in other transactions). Use this
1182         method to perform a locked DDL statement.
1183         """
1184         self.connections.lock("Transaction denied due to DDL: %r" % sql)
1185         try:
1186             if conn is None:
1187                 # Important: use _factory(), not get(), to avoid the lock
1188                 conn = self.connections._factory()
1189             self.execute(sql, conn)
1190         finally:
1191             self.connections.unlock()
1192
1193     def fetch(self, sql, conn=None):
1194         """Return (rowdata, (colname, coltype)) for the given sql.
1195
1196         sql should be a SQL string.
1197
1198         rowdata will be an iterable of iterables containing the result values.
1199         columns will be an iterable of (column name, data type) pairs.
1200
1201         This base class uses _sqlite syntax.
1202         """
1203         res = self.execute(sql, conn)
1204         return res.row_list, res.col_defs
1205
1206     def select(self, query, order=None, limit=None, offset=None,
1207                distinct=False, strict=True):
1208         """Return a Dataset of matching data, coerced to Python types.
1209
1210         strict: if True (the default), a ValueError is raised whenever
1211             the query.restriction cannot be perfectly translated to SQL.
1212             IMPORTANT: if 'strict' is False, the returned Dataset may
1213             contain rows that do not match the given query restriction!
1214             Therefore, the caller is responsible for checking the boolean
1215             Dataset.statement.imperfect flag; if True, each returned row
1216             must be verified, typically by forming complete objects from
1217             each returned row and passing them into the query restriction
1218             (as positional arguments).
1219         """
1220         if not isinstance(query, geniusql.Query):
1221             query = geniusql.Query(*query)
1222
1223         statement = geniusql.Statement(query, order=order, limit=limit,
1224                                        offset=offset, distinct=distinct)
1225         sqlstatement = self.selectwriter(self, statement).statement
1226         if sqlstatement.imperfect:
1227             if strict:
1228                 raise ValueError("The given restriction %r could not safely "
1229                                  "be translated to SQL: %r" %
1230                                  (statement.query.restriction,
1231                                   sqlstatement.sql))
1232             elif limit or offset:
1233                 raise ValueError("The given restriction %r could not safely "
1234                                  "be translated to SQL: %r. Imperfect SQL is "
1235                                  "not yet supported with limit or offset "
1236                                  "(%r, %r)." %
1237                                  (statement.query.restriction,
1238                                   sqlstatement.sql, limit, offset))
1239
1240         data, _ = self.fetch(sqlstatement.sql)
1241         d = Dataset(sqlstatement, data)
1242         return d
1243
1244     def save(self, query):
1245         """Execute the given query as an UPDATE statement."""
1246         if not isinstance(query, geniusql.Query):
1247             query = geniusql.Query(*query)
1248
1249         sel = self.updatewriter(self, query).statement
1250         if sel.imperfect:
1251             raise ValueError("The given restriction could not safely be "
1252                              "translated to SQL.", query.restriction)
1253         self.execute(sel.sql)
1254
1255     def delete(self, query):
1256         """Execute the given query as a DELETE statement."""
1257         if not isinstance(query, geniusql.Query):
1258             query = geniusql.Query(*query)
1259
1260         sel = self.deletewriter(self, query).statement
1261         if sel.imperfect:
1262             raise ValueError("The given restriction could not safely be "
1263                              "translated to SQL.", query.restriction)
1264         self.execute(sel.sql)
1265
1266     def insert(self, query, conn=None):
1267         """Execute the given query as an INSERT statement."""
1268         if not isinstance(query, geniusql.Query):
1269             query = geniusql.Query(*query)
1270
1271         sel = self.insertwriter(self, query).statement
1272         self.execute(sel.sql, conn)
1273
1274     def insert_into(self, name, query, limit=None, distinct=False):
1275         """INSERT matching data INTO a new table and return the Table.
1276
1277         The 'name' argument will be used to name the new table in the
1278         database. The new table will be returned, and will also be added
1279         to the schema; the key will be the provided name. If you want to
1280         use a different key for this table, call schema.alias(name, newkey)
1281         after this method.
1282         """
1283         if not isinstance(query, geniusql.Query):
1284             query = geniusql.Query(*query)
1285
1286         relation = query.relation
1287         if isinstance(relation, geniusql.Join):
1288             if isinstance(relation.table1, geniusql.Join):
1289                 schema = relation.table2.schema
1290             else:
1291                 schema = relation.table1.schema
1292         elif isinstance(relation, Schema):
1293             # This is how we say we want to SELECT scalars (no FROM clause)
1294             schema = relation
1295         else:
1296             schema = relation.schema
1297
1298         statement = geniusql.Statement(query, limit=limit, distinct=distinct)
1299         sqlstatement = self.selectwriter(self, statement).statement
1300         newtable = sqlstatement.result_table(schema, name)
1301
1302         selsql = sqlstatement.sql
1303         if sqlstatement.imperfect:
1304             if limit:
1305                 raise ValueError("The given restriction could not safely "
1306                                  "be translated to SQL. Imperfect SQL is "
1307                                  "not yet supported with limit.",
1308                                  statement.query.restriction, limit)
1309             else:
1310                 errors.warn("The requested INSERT INTO could not produce perfect "
1311                             "SQL. The creation of the new table could take a "
1312                             "long time, since it must fetch each row and INSERT "
1313                             "it into the new table manually. %r" % selsql)
1314
1315             # CREATE TABLE
1316             newtable.schema[name] = newtable
1317             # SELECT ALL
1318             data, _ = self.fetch(selsql)
1319
1320             output_keys = [names[0] for names in sqlstatement.output]
1321             for row in Dataset(sqlstatement, data):
1322                 row = dict(zip(output_keys, row))
1323                 # Run a dummy object through our restriction before inserting.
1324                 if not query.restriction(_ImperfectDummy(**row)):
1325                     continue
1326                 # INSERT INTO
1327                 newtable.insert(**row)
1328         else:
1329             # CREATE TABLE
1330             newtable.schema[name] = newtable
1331             qnames = [names[2] for names in sqlstatement.output]
1332             sql = ("INSERT INTO %s (%s) %s" %
1333                    (newtable.qname, ", ".join(qnames), selsql))
1334             self.execute(sql)
1335
1336         return newtable
1337
1338
1339
1340 # ------------------------------- Datasets ------------------------------- #
1341
1342
1343 class Dataset(object):
1344     """A populated relation; the result of a SELECT.
1345
1346     IMPORTANT: This is designed to be used with layers built on top of
1347     Geniusql that wish to do their own objectification of the returned
1348     rows. The iterator skips checking the 'imperfect' flag on the
1349     statement, under the assumption that the caller will do so itself.
1350     """
1351
1352     def __init__(self, statement, data):
1353         self.statement = statement
1354         self.data = data
1355         # pre-fetch cols and cache in an optimal format.
1356         self.cols = [coldata[3] for coldata in statement.output]
1357
1358     def __iter__(self):
1359         """Return an iterator over self."""
1360         return DatasetIterator(self)
1361
1362     def scalar(self, col=0):
1363         """Return the Nth value (0-based) of the first row of these results."""
1364         for row in self:
1365             return row[col]
1366
1367
1368 class DatasetIterator(object):
1369     """An iterator for Dataset objects."""
1370
1371     def __init__(self, dataset):
1372         self.dataset = dataset
1373         self.dataiter = iter(dataset.data)
1374
1375     def __iter__(self):
1376         """Return an iterator over self."""
1377         return self
1378
1379     def next(self):
1380         """Return the next row in the sequence (without checking imperfect!).
1381
1382         This is designed to be used with layers built on top of Geniusql
1383         that wish to do their own objectification of the returned rows.
1384         This method skips checking the 'imperfect' flag on the statement,
1385         under the assumption that the caller will do so itself.
1386         """
1387         raw_row = self.dataiter.next()
1388         return [col.adapter.pull(val, col.dbtype) for val, col
1389                 in zip(raw_row, self.dataset.cols)]
1390
Note: See TracBrowser for help on using the browser.