]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Columns now have default= and server_default=. PassiveDefault fades away.
authorJason Kirtland <jek@discorporate.us>
Wed, 14 May 2008 19:49:40 +0000 (19:49 +0000)
committerJason Kirtland <jek@discorporate.us>
Wed, 14 May 2008 19:49:40 +0000 (19:49 +0000)
27 files changed:
doc/build/content/metadata.txt
lib/sqlalchemy/__init__.py
lib/sqlalchemy/databases/access.py
lib/sqlalchemy/databases/firebird.py
lib/sqlalchemy/databases/information_schema.py
lib/sqlalchemy/databases/informix.py
lib/sqlalchemy/databases/maxdb.py
lib/sqlalchemy/databases/mssql.py
lib/sqlalchemy/databases/mysql.py
lib/sqlalchemy/databases/oracle.py
lib/sqlalchemy/databases/postgres.py
lib/sqlalchemy/databases/sqlite.py
lib/sqlalchemy/databases/sybase.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/schema.py
lib/sqlalchemy/sql/compiler.py
test/dialect/mysql.py
test/dialect/postgres.py
test/dialect/sqlite.py
test/engine/reflection.py
test/orm/assorted_eager.py
test/orm/unitofwork.py
test/perf/ormsession.py
test/sql/defaults.py
test/sql/query.py
test/testlib/requires.py
test/testlib/testing.py

index 151a7ce5edbfaa580fc43ce5b090049a91e47136..26eab259419335b644fbd039909099b1d0c13988 100644 (file)
@@ -361,25 +361,39 @@ The above SQL functions are usually executed "inline" with the INSERT or UPDATE
 
 For a statement execution which is not an executemany, the returned `ResultProxy` will contain a collection accessible via `result.postfetch_cols()` which contains a list of all `Column` objects which had an inline-executed default.  Similarly, all parameters which were bound to the statement, including all Python and SQL expressions which were pre-executed, are present in the `last_inserted_params()` or `last_updated_params()` collections on `ResultProxy`.  The `last_inserted_ids()` collection contains a list of primary key values for the row inserted.  
 
-#### DDL-Level Defaults {@name=passive}    
+#### DDL-Level Defaults {@name=passive}
 
-A variant on a SQL expression default is the `PassiveDefault`, which gets placed in the CREATE TABLE statement during a `create()` operation:
+A variant on a SQL expression default is the `server_default`, which gets placed in the CREATE TABLE statement during a `create()` operation:
 
     {python}
-    t = Table('test', meta, 
-        Column('mycolumn', DateTime, PassiveDefault(text("sysdate")))
+    t = Table('test', meta,
+        Column('abc', String(20), server_default='abc'),
+        Column('created_at', DateTime, server_default=text("sysdate"))
     )
-        
+
 A create call for the above table will produce:
 
     {code}
     CREATE TABLE test (
-        mycolumn datetime default sysdate
+        abc varchar(20) default 'abc',
+        created_at datetime default sysdate
     )
-        
-The behavior of `PassiveDefault` is similar to that of a regular SQL default; if it's placed on a primary key column for a database which doesn't have a way to "postfetch" the ID, and the statement is not "inlined", the SQL expression is pre-executed; otherwise, SQLAlchemy lets the default fire off on the database side normally.
 
-#### Defining Sequences {@name=sequences}    
+The behavior of `server_default` is similar to that of a regular SQL default; if it's placed on a primary key column for a database which doesn't have a way to "postfetch" the ID, and the statement is not "inlined", the SQL expression is pre-executed; otherwise, SQLAlchemy lets the default fire off on the database side normally.
+
+#### Triggered Columns {@name=triggered}
+
+Columns with values set by a database trigger or other external process may be called out with a marker:
+
+    {python}
+    t = Table('test', meta,
+        Column('abc', String(20), server_default=FetchedValue())
+        Column('def', String(20), server_onupdate=FetchedValue())
+    )
+
+These markers do not emit a ``default`` clause when the table is created, however they do set the same internal flags as a static `server_default` clause, providing hints to higher-level tools that a "post-fetch" of these rows should be performed after an insert or update.
+
+#### Defining Sequences {@name=sequences}
 
 A table with a sequence looks like:
 
index e33451611f939bee16959ab679b3b990003bac9c..28c51d6e8075c32792548191746a03ecab33f268 100644 (file)
@@ -29,7 +29,7 @@ from sqlalchemy.schema import \
     MetaData, ThreadLocalMetaData, Table, Column, ForeignKey, \
     Sequence, Index, ForeignKeyConstraint, PrimaryKeyConstraint, \
     CheckConstraint, UniqueConstraint, Constraint, \
-    PassiveDefault, ColumnDefault, DDL
+    DefaultClause, FetchedValue, PassiveDefault, ColumnDefault, DDL
 
 from sqlalchemy.engine import create_engine, engine_from_config
 
index aa65985d40715190ed44bb6b5ce34641ab71a513..f334522001f5381cd41d7184c2627f77df68a201 100644 (file)
@@ -22,7 +22,7 @@ class AcNumeric(types.Numeric):
             else:
                 return str(value)
         return process
-        
+
     def get_col_spec(self):
         return "NUMERIC"
 
@@ -37,7 +37,7 @@ class AcFloat(types.Float):
                 return str(value)
             return None
         return process
-        
+
 class AcInteger(types.Integer):
     def get_col_spec(self):
         return "INTEGER"
@@ -83,7 +83,7 @@ class AcUnicode(types.Unicode):
         return None
 
 class AcChar(types.CHAR):
-    def get_col_spec(self):        
+    def get_col_spec(self):
         return "TEXT" + (self.length and ("(%d)" % self.length) or "")
 
 class AcBinary(types.Binary):
@@ -100,7 +100,7 @@ class AcBoolean(types.Boolean):
                 return None
             return value and True or False
         return process
-        
+
     def bind_processor(self, dialect):
         def process(value):
             if value is True:
@@ -112,7 +112,7 @@ class AcBoolean(types.Boolean):
             else:
                 return value and True or False
         return process
-        
+
 class AcTimeStamp(types.TIMESTAMP):
     def get_col_spec(self):
         return "TIMESTAMP"
@@ -246,7 +246,7 @@ class AccessDialect(default.DefaultDialect):
         except Exception, e:
             return False
 
-    def reflecttable(self, connection, table, include_columns):        
+    def reflecttable(self, connection, table, include_columns):
         # This is defined in the function, as it relies on win32com constants,
         # that aren't imported until dbapi method is called
         if not hasattr(self, 'ischema_names'):
@@ -262,11 +262,11 @@ class AccessDialect(default.DefaultDialect):
                 const.dbBoolean:    AcBoolean,
                 const.dbText:       AcUnicode, # All Access strings are unicode
             }
-            
+
         # A fresh DAO connection is opened for each reflection
         # This is necessary, so we get the latest updates
         dtbs = daoEngine.OpenDatabase(connection.engine.url.database)
-        
+
         try:
             for tbl in dtbs.TableDefs:
                 if tbl.Name.lower() == table.name.lower():
@@ -290,7 +290,7 @@ class AccessDialect(default.DefaultDialect):
                 elif default:
                     if col.Type == const.dbBoolean:
                         default = default == 'Yes' and '1' or '0'
-                    colargs['default'] = schema.PassiveDefault(sql.text(default))
+                    colargs['server_default'] = schema.DefaultClause(sql.text(default))
 
                 table.append_column(schema.Column(col.Name, coltype, **colargs))
 
@@ -316,7 +316,7 @@ class AccessDialect(default.DefaultDialect):
                             col.unique = idx.Unique
                     else:
                         pass # TBD: multi-column indexes
-                
+
 
             for fk in dtbs.Relations:
                 if fk.ForeignTable != table.name:
index d3662ccbfd90b6184c2156be955796a6512e5d88..3464758698ccaa5621e93b78511e6558070534b7 100644 (file)
@@ -550,7 +550,7 @@ class FBDialect(default.DefaultDialect):
                 # the value comes down as "DEFAULT 'value'"
                 assert row['fdefault'].startswith('DEFAULT ')
                 defvalue = row['fdefault'][8:]
-                args.append(schema.PassiveDefault(sql.text(defvalue)))
+                args.append(schema.DefaultClause(sql.text(defvalue)))
 
             col = schema.Column(*args, **kw)
             if kw['primary_key']:
index 20929cf1e9c5a695edd5c405f8482cac3ef8de4e..659bfc33a0ad49e34c9c7bf4f1994167b25c0fe4 100644 (file)
@@ -1,7 +1,7 @@
 import sqlalchemy.sql as sql
 import sqlalchemy.exc as exc
 from sqlalchemy import select, MetaData, Table, Column, String, Integer
-from sqlalchemy.schema import PassiveDefault, ForeignKeyConstraint
+from sqlalchemy.schema import DefaultClause, ForeignKeyConstraint
 
 ischema = MetaData()
 
@@ -30,7 +30,7 @@ columns = Table("columns", ischema,
     Column("numeric_scale", Integer),
     Column("column_default", Integer),
     schema="information_schema")
-    
+
 constraints = Table("table_constraints", ischema,
     Column("table_schema", String),
     Column("table_name", String),
@@ -85,17 +85,17 @@ def table_names(connection, schema):
 
 def reflecttable(connection, table, include_columns, ischema_names):
     key_constraints = pg_key_constraints
-        
+
     if table.schema is not None:
         current_schema = table.schema
     else:
         current_schema = connection.default_schema_name()
-    
-    s = select([columns], 
+
+    s = select([columns],
         sql.and_(columns.c.table_name==table.name,
         columns.c.table_schema==current_schema),
         order_by=[columns.c.ordinal_position])
-        
+
     c = connection.execute(s)
     found_table = False
     while True:
@@ -106,9 +106,9 @@ def reflecttable(connection, table, include_columns, ischema_names):
  #       continue
         found_table = True
         (name, type, nullable, charlen, numericprec, numericscale, default) = (
-            row[columns.c.column_name], 
-            row[columns.c.data_type], 
-            row[columns.c.is_nullable] == 'YES', 
+            row[columns.c.column_name],
+            row[columns.c.data_type],
+            row[columns.c.is_nullable] == 'YES',
             row[columns.c.character_maximum_length],
             row[columns.c.numeric_precision],
             row[columns.c.numeric_scale],
@@ -116,7 +116,7 @@ def reflecttable(connection, table, include_columns, ischema_names):
             )
         if include_columns and name not in include_columns:
             continue
-        
+
         args = []
         for a in (charlen, numericprec, numericscale):
             if a is not None:
@@ -126,9 +126,9 @@ def reflecttable(connection, table, include_columns, ischema_names):
         coltype = coltype(*args)
         colargs = []
         if default is not None:
-            colargs.append(PassiveDefault(sql.text(default)))
+            colargs.append(DefaultClause(sql.text(default)))
         table.append_column(Column(name, coltype, nullable=nullable, *colargs))
-    
+
     if not found_table:
         raise exc.NoSuchTableError(table.name)
 
@@ -156,7 +156,7 @@ def reflecttable(connection, table, include_columns, ischema_names):
             row[colmap[5]],
             row[colmap[6]]
         )
-        #print "type %s on column %s to remote %s.%s.%s" % (type, constrained_column, referred_schema, referred_table, referred_column) 
+        #print "type %s on column %s to remote %s.%s.%s" % (type, constrained_column, referred_schema, referred_table, referred_column)
         if type == 'PRIMARY KEY':
             table.primary_key.add(table.c[constrained_column])
         elif type == 'FOREIGN KEY':
@@ -177,7 +177,6 @@ def reflecttable(connection, table, include_columns, ischema_names):
                 fk[0].append(constrained_column)
             if refspec not in fk[1]:
                 fk[1].append(refspec)
-    
+
     for name, value in fks.iteritems():
-        table.append_constraint(ForeignKeyConstraint(value[0], value[1], name=name))    
-            
+        table.append_constraint(ForeignKeyConstraint(value[0], value[1], name=name))
index c7bc49dbe8b6506ae696c5bd67848e1308e7a243..42c46db7cd3be8b0fbf53d7ec283eefba16a0a3f 100644 (file)
@@ -317,7 +317,7 @@ class InfoDialect(default.DefaultDialect):
 
             colargs = []
             if default is not None:
-                colargs.append(schema.PassiveDefault(sql.text(default)))
+                colargs.append(schema.DefaultClause(sql.text(default)))
 
             table.append_column(schema.Column(name, coltype, nullable = (nullable == 0), *colargs))
 
index 392cde61fbbc14a7e90e1542f3a68c8c18527531..8d4d3bb0c2d0f5760b4c7f6dbb01d08397800012 100644 (file)
@@ -54,8 +54,8 @@ required components such as an Max-aware 'old oracle style' join compiler
 (thetas with (+) outer indicators) are already done and available for
 integration- email the devel list if you're interested in working on
 this.
-"""
 
+"""
 import datetime, itertools, re
 
 from sqlalchemy import exc, schema, sql, util
@@ -648,14 +648,14 @@ class MaxDBDialect(default.DefaultDialect):
                         col_kw['autoincrement'] = True
                     else:
                         # strip current numbering
-                        col_kw['default'] = schema.PassiveDefault(
+                        col_kw['server_default'] = schema.DefaultClause(
                             sql.text('SERIAL'))
                         col_kw['autoincrement'] = True
                 else:
-                    col_kw['default'] = schema.PassiveDefault(
+                    col_kw['server_default'] = schema.DefaultClause(
                         sql.text(func_def))
             elif constant_def is not None:
-                col_kw['default'] = schema.PassiveDefault(sql.text(
+                col_kw['server_default'] = schema.DefaultClause(sql.text(
                     "'%s'" % constant_def.replace("'", "''")))
 
             table.append_column(schema.Column(name, type_instance, **col_kw))
@@ -972,7 +972,7 @@ class MaxDBSchemaGenerator(compiler.SchemaGenerator):
         # Assign DEFAULT SERIAL heuristically
         elif column.primary_key and column.autoincrement:
             # For SERIAL on a non-primary key member, use
-            # PassiveDefault(text('SERIAL'))
+            # DefaultClause(text('SERIAL'))
             try:
                 first = [c for c in column.table.primary_key.columns
                          if (c.autoincrement and
@@ -987,7 +987,7 @@ class MaxDBSchemaGenerator(compiler.SchemaGenerator):
         return ' '.join(colspec)
 
     def get_column_default_string(self, column):
-        if isinstance(column.default, schema.PassiveDefault):
+        if isinstance(column.server_default, schema.DefaultClause):
             if isinstance(column.default.arg, basestring):
                 if isinstance(column.type, sqltypes.Integer):
                     return str(column.default.arg)
@@ -1087,7 +1087,7 @@ def _autoserial_column(table):
                 if col.default.optional:
                     return index, col
             elif (col.default is None or
-                  (not isinstance(col.default, schema.PassiveDefault))):
+                  (not isinstance(col.server_default, schema.DefaultClause))):
                 return index, col
 
     return None, None
index 4e129952fa7367dc320c7670c75aca32fea6cafa..1cacd81ef919e518e89fb1961d1aff39d16a407a 100644 (file)
@@ -37,7 +37,6 @@ Known issues / TODO:
   does **not** work around
 
 """
-
 import datetime, operator, re, sys
 
 from sqlalchemy import sql, schema, exc, util
@@ -604,7 +603,7 @@ class MSSQLDialect(default.DefaultDialect):
                 coltype = coltype(*args)
             colargs = []
             if default is not None:
-                colargs.append(schema.PassiveDefault(sql.text(default)))
+                colargs.append(schema.DefaultClause(sql.text(default)))
 
             table.append_column(schema.Column(name, coltype, nullable=nullable, autoincrement=False, *colargs))
 
index 0f40569b18007672baf7e1237939e3e9d036fdc5..c4e6bf31f1a1178ee0e7258f54e737c2652f3651 100644 (file)
@@ -671,19 +671,18 @@ class MSTimeStamp(sqltypes.TIMESTAMP):
     """MySQL TIMESTAMP type.
 
     To signal the orm to automatically re-select modified rows to retrieve
-    the updated timestamp, add a PassiveDefault to your column specification::
+    the updated timestamp, add a DefaultClause to your column specification::
 
         from sqlalchemy.databases import mysql
         Column('updated', mysql.MSTimeStamp,
-               PassiveDefault(sql.text('CURRENT_TIMESTAMP')))
+               server_default=sql.text('CURRENT_TIMESTAMP'))
 
     The full range of MySQL 4.1+ TIMESTAMP defaults can be specified in
-    the PassiveDefault::
+    the the default:
 
-        PassiveDefault(sql.text('CURRENT TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'))
+        server_default=sql.text('CURRENT TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
 
     """
-
     def get_col_spec(self):
         return "TIMESTAMP"
 
@@ -2228,7 +2227,7 @@ class MySQLSchemaReflector(object):
                     default = sql.text(default)
             else:
                 default = default[1:-1]
-            col_args.append(schema.PassiveDefault(default))
+            col_args.append(schema.DefaultClause(default))
 
         table.append_column(schema.Column(name, type_instance,
                                           *col_args, **col_kw))
index 5bc8a186faa1b6735fbf785d6c981659749d518e..2f69f8fd93e83f2229b9b6f0a15c22d65354f031 100644 (file)
@@ -493,7 +493,7 @@ class OracleDialect(default.DefaultDialect):
 
             colargs = []
             if default is not None:
-                colargs.append(schema.PassiveDefault(sql.text(default)))
+                colargs.append(schema.DefaultClause(sql.text(default)))
 
             table.append_column(schema.Column(colname, coltype, nullable=nullable, *colargs))
 
index 23b0a273e9a2b2a85857a9e4c3c0f5cf6aa645c7..2f4865e96e96d83ac8b1a7889197802f8e5c6e30 100644 (file)
@@ -546,7 +546,7 @@ class PGDialect(default.DefaultDialect):
                         # unconditionally quote the schema name.  this could
                         # later be enhanced to obey quoting rules / "quote schema"
                         default = match.group(1) + ('"%s"' % sch) + '.' + match.group(2) + match.group(3)
-                colargs.append(schema.PassiveDefault(sql.text(default)))
+                colargs.append(schema.DefaultClause(sql.text(default)))
             table.append_column(schema.Column(name, coltype, nullable=nullable, *colargs))
 
 
@@ -777,8 +777,9 @@ class PGDefaultRunner(base.DefaultRunner):
     def get_column_default(self, column, isinsert=True):
         if column.primary_key:
             # pre-execute passive defaults on primary keys
-            if isinstance(column.default, schema.PassiveDefault):
-                return self.execute_string("select %s" % column.default.arg)
+            if (isinstance(column.server_default, schema.DefaultClause) and
+                column.server_default.arg is not None):
+                return self.execute_string("select %s" % column.server_default.arg)
             elif (isinstance(column.type, sqltypes.Integer) and column.autoincrement) and (column.default is None or (isinstance(column.default, schema.Sequence) and column.default.optional)):
                 sch = column.table.schema
                 # TODO: this has to build into the Sequence object so we can get the quoting
index a63741cf7c8404fad49e7a719ad425d1d1284945..98e2acbf43864780da2458abf3f4105bbcff49f0 100644 (file)
@@ -7,7 +7,7 @@
 
 import datetime, re, time
 
-from sqlalchemy import schema, exc, pool, PassiveDefault
+from sqlalchemy import schema, exc, pool, DefaultClause
 from sqlalchemy.engine import default
 import sqlalchemy.types as sqltypes
 import sqlalchemy.util as util
@@ -336,7 +336,7 @@ class SQLiteDialect(default.DefaultDialect):
 
             colargs = []
             if has_default:
-                colargs.append(PassiveDefault('?'))
+                colargs.append(DefaultClause('?'))
             table.append_column(schema.Column(name, coltype, primary_key = primary_key, nullable = nullable, *colargs))
 
         if not found_table:
index 14734c6e0e2717fc6d050a09dd3a916c3cc9fa1a..cc8597068fbd23950c08157ceba931e76cfcef6b 100644 (file)
@@ -599,7 +599,7 @@ class SybaseSQLDialect(default.DefaultDialect):
                 coltype = coltype(*args)
             colargs = []
             if default is not None:
-                colargs.append(schema.PassiveDefault(sql.text(default)))
+                colargs.append(schema.DefaultClause(sql.text(default)))
 
             # any sequences ?
             col = schema.Column(name, coltype, nullable=nullable, primary_key=primary_key, *colargs)
index 0ff1a05a0f0d8f462978b23a27e5158db1df5d1b..72edc4e4bf674ab8ab319450cc8b293f0cf2b637 100644 (file)
@@ -1058,11 +1058,15 @@ class Mapper(object):
                             if self.__should_log_debug:
                                 self.__log_debug("Using polymorphic identity '%s' for insert column '%s'" % (mapper.polymorphic_identity, col.key))
                             value = mapper.polymorphic_identity
-                            if col.default is None or value is not None:
+                            if ((col.default is None and
+                                 col.server_default is None) or
+                                value is not None):
                                 params[col.key] = value
                         else:
                             value = mapper._get_state_attr_by_column(state, col)
-                            if col.default is None or value is not None:
+                            if ((col.default is None and
+                                 col.server_default is None) or
+                                value is not None):
                                 if isinstance(value, sql.ClauseElement):
                                     value_params[col] = value
                                 else:
index 6d1193494b2be61c56407feb8f45c27df71a3e3c..a632a19c96d7f098181f66328a7ab49388aedf6c 100644 (file)
@@ -24,8 +24,9 @@ Two of the elements here also build upon their "syntactic" counterparts, which
 are defined in [sqlalchemy.sql.expression#], specifically
 [sqlalchemy.schema#Table] and [sqlalchemy.schema#Column].  Since these objects
 are part of the SQL expression language, they are usable as components in SQL
-expressions.  """
+expressions.
 
+"""
 import re, inspect
 from sqlalchemy import types, exc, util, databases
 from sqlalchemy.sql import expression, visitors
@@ -36,17 +37,18 @@ __all__ = ['SchemaItem', 'Table', 'Column', 'ForeignKey', 'Sequence', 'Index',
            'ForeignKeyConstraint', 'PrimaryKeyConstraint', 'CheckConstraint',
            'UniqueConstraint', 'DefaultGenerator', 'Constraint', 'MetaData',
            'ThreadLocalMetaData', 'SchemaVisitor', 'PassiveDefault',
-           'ColumnDefault', 'DDL']
+           'DefaulClause', 'FetchedValue', 'ColumnDefault', 'DDL']
+
 
 class SchemaItem(object):
     """Base class for items that define a database schema."""
 
     __metaclass__ = expression._FigureVisitName
     quote = None
-    
+
     def _init_items(self, *args):
         """Initialize the list of child items for this SchemaItem."""
-        
+
         for item in args:
             if item is not None:
                 item._set_parent(self)
@@ -185,16 +187,16 @@ class Table(SchemaItem, expression.TableClause):
 
         quote
           Force quoting of the identifier on or off, based on `True` or
-          `False`.  Defaults to `None`.  This flag is rarely needed, 
+          `False`.  Defaults to `None`.  This flag is rarely needed,
           as quoting is normally applied
           automatically for known reserved words, as well as for
           "case sensitive" identifiers.  An identifier is "case sensitive"
-          if it contains non-lowercase letters, otherwise it's 
+          if it contains non-lowercase letters, otherwise it's
           considered to be "case insensitive".
 
           quote_schema
             same as 'quote' but applies to the schema identifier.
-            
+
         """
         super(Table, self).__init__(name)
         self.metadata = metadata
@@ -454,6 +456,27 @@ class Column(SchemaItem, expression._ColumnClause):
             list or is given a value of None.  The default expression will be
             converted into a ``ColumnDefault`` object upon initialization.
 
+          server_default
+            Defaults to None: A FetchedValue instance, str, Unicode or
+            sqlalchemy.text() string representing the DDL DEFAULT value for
+            the column.
+
+            String types will be emitted as-is, surrounded by single quotes::
+
+              Column('x', Text, server_default="val")
+
+              x TEXT DEFAULT 'val'
+
+            A sqlalchemy.text() expression will be rendered as-is, without
+            quotes::
+
+              Column('y', DateTime, server_default=text('NOW()'))0
+
+              y DATETIME DEFAULT NOW()
+
+            Strings and text() will be converted into a ``DefaultClause``
+            object upon initialization.
+
           _is_oid
             Defaults to False: used internally to indicate that this column is
             used as the quasi-hidden "oid" column
@@ -485,18 +508,18 @@ class Column(SchemaItem, expression._ColumnClause):
             ``INSERT`` statement execution such that they will assume primary
             key values are created in this manner.  If a ``Column`` has an
             explicit ``ColumnDefault`` object (such as via the `default`
-            keyword, or a ``Sequence`` or ``PassiveDefault``), then the value
+            keyword, or a ``Sequence`` or ``DefaultClause``), then the value
             of `autoincrement` is ignored and is assumed to be False.
             `autoincrement` value is only significant for a column with a type
             or subtype of Integer.
 
           quote
             Force quoting of the identifier on or off, based on `True` or
-            `False`.  Defaults to `None`.  This flag is rarely needed, 
+            `False`.  Defaults to `None`.  This flag is rarely needed,
             as quoting is normally applied
             automatically for known reserved words, as well as for
             "case sensitive" identifiers.  An identifier is "case sensitive"
-            if it contains non-lowercase letters, otherwise it's 
+            if it contains non-lowercase letters, otherwise it's
             considered to be "case insensitive".
         """
 
@@ -525,6 +548,8 @@ class Column(SchemaItem, expression._ColumnClause):
         self.nullable = kwargs.pop('nullable', not self.primary_key)
         self._is_oid = kwargs.pop('_is_oid', False)
         self.default = kwargs.pop('default', None)
+        self.server_default = kwargs.pop('server_default', None)
+        self.server_onupdate = kwargs.pop('server_onupdate', None)
         self.index = kwargs.pop('index', None)
         self.unique = kwargs.pop('unique', None)
         self.quote = kwargs.pop('quote', None)
@@ -576,6 +601,8 @@ class Column(SchemaItem, expression._ColumnClause):
             kwarg.append('onupdate')
         if self.default:
             kwarg.append('default')
+        if self.server_default:
+            kwarg.append('server_default')
         return "Column(%s)" % ', '.join(
             [repr(self.name)] + [repr(self.type)] +
             [repr(x) for x in self.foreign_keys if x is not None] +
@@ -630,9 +657,23 @@ class Column(SchemaItem, expression._ColumnClause):
 
         toinit = list(self.args)
         if self.default is not None:
-            toinit.append(ColumnDefault(self.default))
+            if isinstance(self.default, ColumnDefault):
+                toinit.append(self.default)
+            else:
+                toinit.append(ColumnDefault(self.default))
+        if self.server_default is not None:
+            if isinstance(self.server_default, FetchedValue):
+                toinit.append(self.server_default)
+            else:
+                toinit.append(DefaultClause(self.server_default))
         if self.onupdate is not None:
             toinit.append(ColumnDefault(self.onupdate, for_update=True))
+        if self.server_onupdate is not None:
+            if isinstance(self.server_onupdate, FetchedValue):
+                toinit.append(self.server_default)
+            else:
+                toinit.append(DefaultClause(self.server_onupdate,
+                                            for_update=True))
         self._init_items(*toinit)
         self.args = None
 
@@ -643,7 +684,7 @@ class Column(SchemaItem, expression._ColumnClause):
 
         """
         return Column(self.name, self.type, self.default, key = self.key, primary_key = self.primary_key, nullable = self.nullable, _is_oid = self._is_oid, quote=self.quote, index=self.index, autoincrement=self.autoincrement, *[c.copy() for c in self.constraints])
-    
+
     def _make_proxy(self, selectable, name=None):
         """Create a *proxy* for this column.
 
@@ -869,15 +910,6 @@ class DefaultGenerator(SchemaItem):
     def __repr__(self):
         return "DefaultGenerator()"
 
-class PassiveDefault(DefaultGenerator):
-    """A default that takes effect on the database side."""
-
-    def __init__(self, arg, **kwargs):
-        super(PassiveDefault, self).__init__(**kwargs)
-        self.arg = arg
-
-    def __repr__(self):
-        return "PassiveDefault(%s)" % repr(self.arg)
 
 class ColumnDefault(DefaultGenerator):
     """A plain default value on a column.
@@ -887,6 +919,9 @@ class ColumnDefault(DefaultGenerator):
 
     def __init__(self, arg, **kwargs):
         super(ColumnDefault, self).__init__(**kwargs)
+        if isinstance(arg, FetchedValue):
+            raise exc.ArgumentError(
+                "ColumnDefault may not be a server-side default type.")
         if callable(arg):
             arg = self._maybe_wrap_callable(arg)
         self.arg = arg
@@ -970,6 +1005,41 @@ class Sequence(DefaultGenerator):
             bind = _bind_or_error(self)
         bind.drop(self, checkfirst=checkfirst)
 
+
+class FetchedValue(object):
+    """A default that takes effect on the database side."""
+
+    def __init__(self, for_update=False):
+        self.for_update = for_update
+
+    def _set_parent(self, column):
+        self.column = column
+        if self.for_update:
+            self.column.server_onupdate = self
+        else:
+            self.column.server_default = self
+
+    def __repr__(self):
+        return 'FetchedValue(for_update=%r)' % self.for_update
+
+
+class DefaultClause(FetchedValue):
+    """A DDL-specified DEFAULT column value."""
+
+    def __init__(self, arg, for_update=False):
+        util.assert_arg_type(arg, (basestring,
+                                   expression.ClauseElement,
+                                   expression._TextClause), 'arg')
+        super(DefaultClause, self).__init__(for_update)
+        self.arg = arg
+
+    def __repr__(self):
+        return "DefaultClause(%r, for_update=%r)" % (self.arg, self.for_update)
+
+# alias; deprecated starting 0.5.0
+PassiveDefault = DefaultClause
+
+
 class Constraint(SchemaItem):
     """A table-level SQL constraint, such as a KEY.
 
@@ -999,10 +1069,10 @@ class Constraint(SchemaItem):
 
     def __contains__(self, x):
         return x in self.columns
-    
+
     def contains_column(self, col):
         return self.columns.contains_column(col)
-        
+
     def keys(self):
         return self.columns.keys()
 
index 02164d8b634ddd01fd5bde23f062fb4794c42644..8c8374b9a1b54b18710293a72fa6dffbd01baf52 100644 (file)
@@ -680,7 +680,7 @@ class DefaultCompiler(engine.Compiled):
                         else:
                             values.append((c, create_bind_param(c, None)))
                             self.prefetch.append(c)
-                    elif isinstance(c.default, schema.PassiveDefault):
+                    elif c.server_default is not None:
                         if not c.primary_key:
                             self.postfetch.append(c)
                     elif isinstance(c.default, schema.Sequence):
@@ -697,7 +697,10 @@ class DefaultCompiler(engine.Compiled):
                         else:
                             values.append((c, create_bind_param(c, None)))
                             self.prefetch.append(c)
-                    elif isinstance(c.onupdate, schema.PassiveDefault):
+                    elif c.server_onupdate is not None:
+                        self.postfetch.append(c)
+                    # deprecated? or remove?
+                    elif isinstance(c.onupdate, schema.FetchedValue):
                         self.postfetch.append(c)
         return values
 
@@ -802,11 +805,11 @@ class SchemaGenerator(DDLBase):
         return ''
 
     def get_column_default_string(self, column):
-        if isinstance(column.default, schema.PassiveDefault):
-            if isinstance(column.default.arg, basestring):
-                return "'%s'" % column.default.arg
+        if isinstance(column.server_default, schema.DefaultClause):
+            if isinstance(column.server_default.arg, basestring):
+                return "'%s'" % column.server_default.arg
             else:
-                return unicode(self._compile(column.default.arg, None))
+                return unicode(self._compile(column.server_default.arg, None))
         else:
             return None
 
index bdcdf1800b3d2702fc9657cc2e44e8d65d7c1254..f5719ecb1020abd01d5624b4bcf81f30dd92a523 100644 (file)
@@ -394,18 +394,18 @@ class TypesTest(TestBase, AssertsExecutionResults):
                 ([mysql.MSTimeStamp],
                  'TIMESTAMP'),
                 ([mysql.MSTimeStamp,
-                  PassiveDefault(sql.text('CURRENT_TIMESTAMP'))],
+                  DefaultClause(sql.text('CURRENT_TIMESTAMP'))],
                  "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"),
                 ([mysql.MSTimeStamp,
-                  PassiveDefault(sql.text("'1999-09-09 09:09:09'"))],
+                  DefaultClause(sql.text("'1999-09-09 09:09:09'"))],
                  "TIMESTAMP DEFAULT '1999-09-09 09:09:09'"),
                 ([mysql.MSTimeStamp,
-                  PassiveDefault(sql.text("'1999-09-09 09:09:09' "
+                  DefaultClause(sql.text("'1999-09-09 09:09:09' "
                                           "ON UPDATE CURRENT_TIMESTAMP"))],
                  "TIMESTAMP DEFAULT '1999-09-09 09:09:09' "
                  "ON UPDATE CURRENT_TIMESTAMP"),
                 ([mysql.MSTimeStamp,
-                  PassiveDefault(sql.text("CURRENT_TIMESTAMP "
+                  DefaultClause(sql.text("CURRENT_TIMESTAMP "
                                           "ON UPDATE CURRENT_TIMESTAMP"))],
                  "TIMESTAMP DEFAULT CURRENT_TIMESTAMP "
                  "ON UPDATE CURRENT_TIMESTAMP"),
@@ -611,18 +611,18 @@ class TypesTest(TestBase, AssertsExecutionResults):
         """Test reflection of column defaults."""
 
         def_table = Table('mysql_def', MetaData(testing.db),
-            Column('c1', String(10), PassiveDefault('')),
-            Column('c2', String(10), PassiveDefault('0')),
-            Column('c3', String(10), PassiveDefault('abc')))
+            Column('c1', String(10), DefaultClause('')),
+            Column('c2', String(10), DefaultClause('0')),
+            Column('c3', String(10), DefaultClause('abc')))
 
         try:
             def_table.create()
             reflected = Table('mysql_def', MetaData(testing.db),
                               autoload=True)
             for t in def_table, reflected:
-                assert t.c.c1.default.arg == ''
-                assert t.c.c2.default.arg == '0'
-                assert t.c.c3.default.arg == 'abc'
+                assert t.c.c1.server_default.arg == ''
+                assert t.c.c2.server_default.arg == '0'
+                assert t.c.c3.server_default.arg == 'abc'
         finally:
             def_table.drop()
 
@@ -697,39 +697,39 @@ class TypesTest(TestBase, AssertsExecutionResults):
         try:
             Table('ai_1', meta,
                   Column('int_y', Integer, primary_key=True),
-                  Column('int_n', Integer, PassiveDefault('0'),
+                  Column('int_n', Integer, DefaultClause('0'),
                          primary_key=True))
             Table('ai_2', meta,
                   Column('int_y', Integer, primary_key=True),
-                  Column('int_n', Integer, PassiveDefault('0'),
+                  Column('int_n', Integer, DefaultClause('0'),
                          primary_key=True))
             Table('ai_3', meta,
-                  Column('int_n', Integer, PassiveDefault('0'),
+                  Column('int_n', Integer, DefaultClause('0'),
                          primary_key=True, autoincrement=False),
                   Column('int_y', Integer, primary_key=True))
             Table('ai_4', meta,
-                  Column('int_n', Integer, PassiveDefault('0'),
+                  Column('int_n', Integer, DefaultClause('0'),
                          primary_key=True, autoincrement=False),
-                  Column('int_n2', Integer, PassiveDefault('0'),
+                  Column('int_n2', Integer, DefaultClause('0'),
                          primary_key=True, autoincrement=False))
             Table('ai_5', meta,
                   Column('int_y', Integer, primary_key=True),
-                  Column('int_n', Integer, PassiveDefault('0'),
+                  Column('int_n', Integer, DefaultClause('0'),
                          primary_key=True, autoincrement=False))
             Table('ai_6', meta,
-                  Column('o1', String(1), PassiveDefault('x'),
+                  Column('o1', String(1), DefaultClause('x'),
                          primary_key=True),
                   Column('int_y', Integer, primary_key=True))
             Table('ai_7', meta,
-                  Column('o1', String(1), PassiveDefault('x'),
+                  Column('o1', String(1), DefaultClause('x'),
                          primary_key=True),
-                  Column('o2', String(1), PassiveDefault('x'),
+                  Column('o2', String(1), DefaultClause('x'),
                          primary_key=True),
                   Column('int_y', Integer, primary_key=True))
             Table('ai_8', meta,
-                  Column('o1', String(1), PassiveDefault('x'),
+                  Column('o1', String(1), DefaultClause('x'),
                          primary_key=True),
-                  Column('o2', String(1), PassiveDefault('x'),
+                  Column('o2', String(1), DefaultClause('x'),
                          primary_key=True))
             meta.create_all()
 
index 83ca6c6f6343e6d5a91a9bccd74e1eaae5903950..88bc0bb99ccfd6da6711bce9c3d2fa90f4f610f4 100644 (file)
@@ -411,7 +411,7 @@ class DomainReflectionTest(TestBase, AssertsExecutionResults):
     def test_domain_is_reflected(self):
         metadata = MetaData(testing.db)
         table = Table('testtable', metadata, autoload=True)
-        self.assertEquals(str(table.columns.answer.default.arg), '42', "Reflected default value didn't equal expected value")
+        self.assertEquals(str(table.columns.answer.server_default.arg), '42', "Reflected default value didn't equal expected value")
         self.assertFalse(table.columns.answer.nullable, "Expected reflected column to not be nullable.")
 
     def test_table_is_reflected_alt_schema(self):
@@ -423,13 +423,13 @@ class DomainReflectionTest(TestBase, AssertsExecutionResults):
     def test_schema_domain_is_reflected(self):
         metadata = MetaData(testing.db)
         table = Table('testtable', metadata, autoload=True, schema='alt_schema')
-        self.assertEquals(str(table.columns.answer.default.arg), '0', "Reflected default value didn't equal expected value")
+        self.assertEquals(str(table.columns.answer.server_default.arg), '0', "Reflected default value didn't equal expected value")
         self.assertTrue(table.columns.answer.nullable, "Expected reflected column to be nullable.")
 
     def test_crosschema_domain_is_reflected(self):
         metadata = MetaData(testing.db)
         table = Table('crosschema', metadata, autoload=True)
-        self.assertEquals(str(table.columns.answer.default.arg), '0', "Reflected default value didn't equal expected value")
+        self.assertEquals(str(table.columns.answer.server_default.arg), '0', "Reflected default value didn't equal expected value")
         self.assertTrue(table.columns.answer.nullable, "Expected reflected column to be nullable.")
 
 class MiscTest(TestBase, AssertsExecutionResults):
@@ -568,10 +568,10 @@ class MiscTest(TestBase, AssertsExecutionResults):
             self.assert_((subject.c.id==referer.c.ref).compare(subject.join(referer).onclause))
         finally:
             meta1.drop_all()
-    
+
     def test_schema_roundtrips(self):
         meta = MetaData(testing.db)
-        users = Table('users', meta, 
+        users = Table('users', meta,
             Column('id', Integer, primary_key=True),
             Column('name', String(50)), schema='alt_schema')
         users.create()
@@ -580,23 +580,23 @@ class MiscTest(TestBase, AssertsExecutionResults):
             users.insert().execute(id=2, name='name2')
             users.insert().execute(id=3, name='name3')
             users.insert().execute(id=4, name='name4')
-            
+
             self.assertEquals(users.select().where(users.c.name=='name2').execute().fetchall(), [(2, 'name2')])
             self.assertEquals(users.select(use_labels=True).where(users.c.name=='name2').execute().fetchall(), [(2, 'name2')])
-            
+
             users.delete().where(users.c.id==3).execute()
             self.assertEquals(users.select().where(users.c.name=='name3').execute().fetchall(), [])
-            
+
             users.update().where(users.c.name=='name4').execute(name='newname')
             self.assertEquals(users.select(use_labels=True).where(users.c.id==4).execute().fetchall(), [(4, 'newname')])
-            
+
         finally:
             users.drop()
-            
+
     def test_preexecute_passivedefault(self):
         """test that when we get a primary key column back
         from reflecting a table which has a default value on it, we pre-execute
-        that PassiveDefault upon insert."""
+        that DefaultClause upon insert."""
 
         try:
             meta = MetaData(testing.db)
@@ -739,7 +739,7 @@ class ArrayTest(TestBase, AssertsExecutionResults):
         mapper(Foo, footable)
         metadata.create_all()
         sess = create_session()
-        
+
         foo = Foo()
         foo.id = 1
         foo.intarr = [1,2,3]
@@ -748,18 +748,18 @@ class ArrayTest(TestBase, AssertsExecutionResults):
         sess.clear()
         foo = sess.query(Foo).get(1)
         self.assertEquals(foo.intarr, [1,2,3])
-        
+
         foo.intarr.append(4)
         sess.flush()
         sess.clear()
         foo = sess.query(Foo).get(1)
         self.assertEquals(foo.intarr, [1,2,3,4])
-        
+
         foo.intarr = []
         sess.flush()
         sess.clear()
         self.assertEquals(foo.intarr, [])
-        
+
         foo.intarr = None
         sess.flush()
         sess.clear()
@@ -771,25 +771,25 @@ class ArrayTest(TestBase, AssertsExecutionResults):
         sess.save(foo)
         sess.flush()
 
-class TimeStampTest(TestBase, AssertsExecutionResults): 
-    __only_on__ = 'postgres' 
-    def test_timestamp(self): 
+class TimeStampTest(TestBase, AssertsExecutionResults):
+    __only_on__ = 'postgres'
+    def test_timestamp(self):
         engine = testing.db
-        connection = engine.connect() 
-        s = select([func.TIMESTAMP("12/25/07").label("ts")]) 
-        result = connection.execute(s).fetchone() 
-        self.assertEqual(result[0], datetime.datetime(2007, 12, 25, 0, 0)) 
+        connection = engine.connect()
+        s = select([func.TIMESTAMP("12/25/07").label("ts")])
+        result = connection.execute(s).fetchone()
+        self.assertEqual(result[0], datetime.datetime(2007, 12, 25, 0, 0))
 
 class ServerSideCursorsTest(TestBase, AssertsExecutionResults):
     __only_on__ = 'postgres'
-    
+
     def setUpAll(self):
         global ss_engine
         ss_engine = engines.testing_engine(options={'server_side_cursors':True})
-        
+
     def tearDownAll(self):
         ss_engine.dispose()
-    
+
     def test_roundtrip(self):
         test_table = Table('test_table', MetaData(ss_engine),
             Column('id', Integer, primary_key=True),
@@ -798,19 +798,19 @@ class ServerSideCursorsTest(TestBase, AssertsExecutionResults):
         test_table.create(checkfirst=True)
         try:
             test_table.insert().execute(data='data1')
-            
+
             nextid = ss_engine.execute(Sequence('test_table_id_seq'))
             test_table.insert().execute(id=nextid, data='data2')
-            
+
             self.assertEquals(test_table.select().execute().fetchall(), [(1, 'data1'), (2, 'data2')])
-            
+
             test_table.update().where(test_table.c.id==2).values(data=test_table.c.data + ' updated').execute()
             self.assertEquals(test_table.select().execute().fetchall(), [(1, 'data1'), (2, 'data2 updated')])
             test_table.delete().execute()
             self.assertEquals(test_table.count().scalar(), 0)
         finally:
             test_table.drop(checkfirst=True)
-            
-    
+
+
 if __name__ == "__main__":
     testenv.main()
index 795146b093836d9975f82fcaaafae2607d286afc..c493b8d438e83ac4a87f69e64c1b6d764b231552 100644 (file)
@@ -260,7 +260,7 @@ class InsertTest(TestBase, AssertsExecutionResults):
             self._test_empty_insert,
             Table('c', MetaData(testing.db),
                   Column('x', Integer, primary_key=True),
-                  Column('y', Integer, PassiveDefault('123'),
+                  Column('y', Integer, DefaultClause('123'),
                          primary_key=True)))
 
     @testing.exclude('sqlite', '<', (3, 4), 'no database support')
@@ -268,7 +268,7 @@ class InsertTest(TestBase, AssertsExecutionResults):
         self._test_empty_insert(
             Table('d', MetaData(testing.db),
                   Column('x', Integer, primary_key=True),
-                  Column('y', Integer, PassiveDefault('123'))))
+                  Column('y', Integer, DefaultClause('123'))))
 
     @testing.exclude('sqlite', '<', (3, 4), 'no database support')
     def test_empty_insert_nopk1(self):
index e4ef21e378f5c39833d3796b8052c8f49d538f8d..ebb79154e90c3c8d051b341337e61fa41936de46 100644 (file)
@@ -27,7 +27,7 @@ class ReflectionTest(TestBase, ComparesTables):
             Column('test6', sa.DateTime, nullable=False),
             Column('test7', sa.Text),
             Column('test8', sa.Binary),
-            Column('test_passivedefault2', sa.Integer, sa.PassiveDefault("5")),
+            Column('test_passivedefault2', sa.Integer, server_default='5'),
             Column('test9', sa.Binary(100)),
             Column('test_numeric', sa.Numeric()),
             test_needs_fk=True,
index c8c0f6a7231a1cf5963313020080c48aad81c936..76fcd869d2261cce4754b343fe06ecd88c16da03 100644 (file)
@@ -19,7 +19,7 @@ class EagerTest(_base.MappedTest):
     
     def define_tables(self, metadata):
         # determine a literal value for "false" based on the dialect
-        # FIXME: this PassiveDefault setup is bogus.
+        # FIXME: this DefaultClause setup is bogus.
 
         dialect = testing.db.dialect
         bp = sa.Boolean().dialect_impl(dialect).bind_processor(dialect)
@@ -52,7 +52,7 @@ class EagerTest(_base.MappedTest):
                      primary_key=True, nullable=False),
               Column('owner_id', Integer, ForeignKey('owners.id'),
                      primary_key=True, nullable=False),
-              Column('someoption', sa.Boolean, sa.PassiveDefault(false),
+              Column('someoption', sa.Boolean, server_default=false,
                      nullable=False))
 
     def setup_classes(self):
index f9be94c98ba5671228c865d9103ab24673546ec8..a6ff5cc7633d9f6e55f575901770b6220983f560 100644 (file)
@@ -835,7 +835,7 @@ class DefaultTest(_base.MappedTest):
         dt = Table('default_t', metadata,
             Column('id', Integer, primary_key=True,
                    test_needs_autoincrement=True),
-            Column('hoho', hohotype, sa.PassiveDefault(str(hohoval))),
+            Column('hoho', hohotype, server_default=str(hohoval)),
             Column('counter', Integer, default=sa.func.length("1234567")),
             Column('foober', String(30), default="im foober",
                    onupdate="im the update"))
@@ -919,7 +919,7 @@ class DefaultTest(_base.MappedTest):
 
     @testing.resolve_artifact_names
     def test_insert_nopostfetch(self):
-        # populates the PassiveDefaults explicitly so there is no
+        # populates from the FetchValues explicitly so there is no
         # "post-update"
         mapper(Hoho, default_t)
 
index b0187a78717259d8faec9d3fcac7f461dd80e635..d8ae187e5660ab62f287677306e25ef1e76412aa 100644 (file)
@@ -36,7 +36,7 @@ def define_tables():
                      Column('id', Integer, primary_key=True),
                      Column('item_id', Integer, ForeignKey('items.id'),
                             nullable=False),
-                     Column('name', String(100), PassiveDefault('no name')),
+                     Column('name', String(100), server_default='no name'),
                      test_needs_acid=True)
     customers = Table('customers', metadata,
                       Column('id', Integer, primary_key=True),
index 3ba8c7d0202587e1c80234a6cba3511a0a3966cf..dafab1fd02a65a31c78a9b444cae2067bb84cb0d 100644 (file)
@@ -3,6 +3,8 @@ import datetime
 from sqlalchemy import *
 from sqlalchemy import exc, schema, util
 from sqlalchemy.orm import mapper, create_session
+from testlib import sa, testing
+from testlib.testing import eq_
 from testlib import *
 
 
@@ -35,8 +37,9 @@ class DefaultTest(TestBase):
         use_function_defaults = testing.against('postgres', 'oracle')
         is_oracle = testing.against('oracle')
 
-        # select "count(1)" returns different results on different DBs
-        # also correct for "current_date" compatible as column default, value differences
+        # select "count(1)" returns different results on different DBs also
+        # correct for "current_date" compatible as column default, value
+        # differences
         currenttime = func.current_date(type_=Date, bind=db)
 
         if is_oracle:
@@ -69,32 +72,48 @@ class DefaultTest(TestBase):
 
         t = Table('default_test1', metadata,
             # python function
-            Column('col1', Integer, primary_key=True, default=mydefault),
+            Column('col1', Integer, primary_key=True,
+                   default=mydefault),
 
             # python literal
-            Column('col2', String(20), default="imthedefault", onupdate="im the update"),
+            Column('col2', String(20),
+                   default="imthedefault",
+                   onupdate="im the update"),
 
             # preexecute expression
-            Column('col3', Integer, default=func.length('abcdef'), onupdate=func.length('abcdefghijk')),
+            Column('col3', Integer,
+                   default=func.length('abcdef'),
+                   onupdate=func.length('abcdefghijk')),
 
             # SQL-side default from sql expression
-            Column('col4', deftype, PassiveDefault(def1)),
+            Column('col4', deftype,
+                   server_default=def1),
 
             # SQL-side default from literal expression
-            Column('col5', deftype, PassiveDefault(def2)),
+            Column('col5', deftype,
+                   server_default=def2),
 
             # preexecute + update timestamp
-            Column('col6', Date, default=currenttime, onupdate=currenttime),
+            Column('col6', Date,
+                   default=currenttime,
+                   onupdate=currenttime),
 
             Column('boolcol1', Boolean, default=True),
             Column('boolcol2', Boolean, default=False),
 
             # python function which uses ExecutionContext
-            Column('col7', Integer, default=mydefault_using_connection, onupdate=myupdate_with_ctx),
+            Column('col7', Integer,
+                   default=mydefault_using_connection,
+                   onupdate=myupdate_with_ctx),
 
             # python builtin
-            Column('col8', Date, default=datetime.date.today, onupdate=datetime.date.today)
-        )
+            Column('col8', Date,
+                   default=datetime.date.today,
+                   onupdate=datetime.date.today),
+            # combo
+            Column('col9', String(20),
+                   default='py',
+                   server_default='ddl'))
         t.create()
 
     def tearDownAll(self):
@@ -159,29 +178,98 @@ class DefaultTest(TestBase):
         self.assert_(z == f)
         self.assert_(f2==11)
 
-    def testinsert(self):
+    def test_py_vs_server_default_detection(self):
+
+        def has_(name, *wanted):
+            slots = ['default', 'onupdate', 'server_default', 'server_onupdate']
+            col = tbl.c[name]
+            for slot in wanted:
+                slots.remove(slot)
+                assert getattr(col, slot) is not None, getattr(col, slot)
+            for slot in slots:
+                assert getattr(col, slot) is None, getattr(col, slot)
+
+        tbl = t
+        has_('col1', 'default')
+        has_('col2', 'default', 'onupdate')
+        has_('col3', 'default', 'onupdate')
+        has_('col4', 'server_default')
+        has_('col5', 'server_default')
+        has_('col6', 'default', 'onupdate')
+        has_('boolcol1', 'default')
+        has_('boolcol2', 'default')
+        has_('col7', 'default', 'onupdate')
+        has_('col8', 'default', 'onupdate')
+        has_('col9', 'default', 'server_default')
+
+        t2 = Table('t2', MetaData(),
+                   Column('col1', Integer, Sequence('foo')),
+                   Column('col2', Integer,
+                          default=Sequence('foo'),
+                          server_default='y'),
+                   Column('col3', Integer,
+                          Sequence('foo'),
+                          server_default='x'),
+                   Column('col4', Integer,
+                          ColumnDefault('x'),
+                          DefaultClause('y')),
+                   Column('col4', Integer,
+                          ColumnDefault('x'),
+                          DefaultClause('y'),
+                          DefaultClause('y', for_update=True)),
+                   Column('col5', Integer,
+                          ColumnDefault('x'),
+                          DefaultClause('y'),
+                          onupdate='z'),
+                   Column('col6', Integer,
+                          ColumnDefault('x'),
+                          server_default='y',
+                          onupdate='z'),
+                   Column('col7', Integer,
+                          default='x',
+                          server_default='y',
+                          onupdate='z'),
+                   Column('col8', Integer,
+                          server_onupdate='u',
+                          default='x',
+                          server_default='y',
+                          onupdate='z'))
+        tbl = t2
+        has_('col1', 'default')
+        has_('col2', 'default', 'server_default')
+        has_('col3', 'default', 'server_default')
+        has_('col4', 'default', 'server_default', 'server_onupdate')
+        has_('col5', 'default', 'server_default', 'onupdate')
+        has_('col6', 'default', 'server_default', 'onupdate')
+        has_('col7', 'default', 'server_default', 'onupdate')
+        has_('col8', 'default', 'server_default', 'onupdate', 'server_onupdate')
+
+    def test_insert(self):
         r = t.insert().execute()
         assert r.lastrow_has_defaults()
-        assert util.Set(r.context.postfetch_cols) == util.Set([t.c.col3, t.c.col5, t.c.col4, t.c.col6])
+        eq_(util.Set(r.context.postfetch_cols), util.Set([t.c.col3, t.c.col5, t.c.col4, t.c.col6]))
 
         r = t.insert(inline=True).execute()
         assert r.lastrow_has_defaults()
-        assert util.Set(r.context.postfetch_cols) == util.Set([t.c.col3, t.c.col5, t.c.col4, t.c.col6])
+        eq_(util.Set(r.context.postfetch_cols), util.Set([t.c.col3, t.c.col5, t.c.col4, t.c.col6]))
 
-        t.insert().execute()
         t.insert().execute()
 
         ctexec = select([currenttime.label('now')], bind=testing.db).scalar()
         l = t.select().execute()
         today = datetime.date.today()
-        self.assertEquals(l.fetchall(), [
-            (51, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today),
-            (52, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today),
-            (53, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today),
-            (54, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today),
-            ])
-
-    def testinsertmany(self):
+        eq_(l.fetchall(), [
+            (x, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today, 'py')
+            for x in range(51, 54)])
+
+        t.insert().execute(col9=None)
+        assert r.lastrow_has_defaults()
+        eq_(util.Set(r.context.postfetch_cols), util.Set([t.c.col3, t.c.col5, t.c.col4, t.c.col6]))
+
+        eq_(t.select(t.c.col1==54).execute().fetchall(),
+            [(54, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today, None)])
+
+    def test_insertmany(self):
         # MySQL-Python 1.2.2 breaks functions in execute_many :(
         if (testing.against('mysql') and
             testing.db.dialect.dbapi.version_info[:3] == (1, 2, 2)):
@@ -192,14 +280,17 @@ class DefaultTest(TestBase):
         ctexec = currenttime.scalar()
         l = t.select().execute()
         today = datetime.date.today()
-        self.assert_(l.fetchall() == [(51, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today), (52, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today), (53, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today)])
+        eq_(l.fetchall(),
+            [(51, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today, 'py'),
+             (52, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today, 'py'),
+             (53, 'imthedefault', f, ts, ts, ctexec, True, False, 12, today, 'py')])
 
-    def testinsertvalues(self):
+    def test_insert_values(self):
         t.insert(values={'col3':50}).execute()
         l = t.select().execute()
         self.assert_(l.fetchone()['col3'] == 50)
 
-    def testupdatemany(self):
+    def test_updatemany(self):
         # MySQL-Python 1.2.2 breaks functions in execute_many :(
         if (testing.against('mysql') and
             testing.db.dialect.dbapi.version_info[:3] == (1, 2, 2)):
@@ -220,7 +311,10 @@ class DefaultTest(TestBase):
         l = t.select().execute()
         ctexec = currenttime.scalar()
         today = datetime.date.today()
-        self.assert_(l.fetchall() == [(51, 'im the update', f2, ts, ts, ctexec, False, False, 13, today), (52, 'im the update', f2, ts, ts, ctexec, True, False, 13, today), (53, 'im the update', f2, ts, ts, ctexec, True, False, 13, today)])
+        eq_(l.fetchall(),
+            [(51, 'im the update', f2, ts, ts, ctexec, False, False, 13, today, 'py'),
+             (52, 'im the update', f2, ts, ts, ctexec, True, False, 13, today, 'py'),
+             (53, 'im the update', f2, ts, ts, ctexec, True, False, 13, today, 'py')])
 
     def testupdate(self):
         r = t.insert().execute()
@@ -229,7 +323,7 @@ class DefaultTest(TestBase):
         ctexec = currenttime.scalar()
         l = t.select(t.c.col1==pk).execute()
         l = l.fetchone()
-        self.assert_(l == (pk, 'im the update', f2, None, None, ctexec, True, False, 13, datetime.date.today()))
+        self.assert_(l == (pk, 'im the update', f2, None, None, ctexec, True, False, 13, datetime.date.today(), 'py'))
         self.assert_(f2==11)
 
     def testupdatevalues(self):
@@ -242,12 +336,15 @@ class DefaultTest(TestBase):
 
     @testing.fails_on_everything_except('postgres')
     def testpassiveoverride(self):
-        """primarily for postgres, tests that when we get a primary key column back
-        from reflecting a table which has a default value on it, we pre-execute
-        that PassiveDefault upon insert, even though PassiveDefault says
-        "let the database execute this", because in postgres we must have all the primary
-        key values in memory before insert; otherwise we cant locate the just inserted row."""
-
+        """
+        Primarily for postgres, tests that when we get a primary key column
+        back from reflecting a table which has a default value on it, we
+        pre-execute that DefaultClause upon insert, even though DefaultClause
+        says "let the database execute this", because in postgres we must have
+        all the primary key values in memory before insert; otherwise we can't
+        locate the just inserted row.
+
+        """
         try:
             meta = MetaData(testing.db)
             testing.db.execute("""
index 4a1d672cdf29d4aa4a7cc0f23455426fb3260531..098e709b23011a5e3f6f0c08853aaf20cb36e7a7 100644 (file)
@@ -84,7 +84,7 @@ class QueryTest(TestBase):
                 Table("t2", metadata,
                     Column('id', Integer, Sequence('t2_id_seq', optional=True), primary_key=True),
                     Column('foo', String(30), primary_key=True),
-                    Column('bar', String(30), PassiveDefault('hi'))
+                    Column('bar', String(30), server_default='hi')
                 ),
                 {'foo':'hi'},
                 {'id':1, 'foo':'hi', 'bar':'hi'}
@@ -104,7 +104,7 @@ class QueryTest(TestBase):
                 Table("t4", metadata,
                     Column('id', Integer, Sequence('t4_id_seq', optional=True), primary_key=True),
                     Column('foo', String(30), primary_key=True),
-                    Column('bar', String(30), PassiveDefault('hi'))
+                    Column('bar', String(30), server_default='hi')
                 ),
                 {'foo':'hi', 'id':1},
                 {'id':1, 'foo':'hi', 'bar':'hi'}
@@ -113,7 +113,7 @@ class QueryTest(TestBase):
                 {'unsupported':[]},
                 Table("t5", metadata,
                     Column('id', String(10), primary_key=True),
-                    Column('bar', String(30), PassiveDefault('hi'))
+                    Column('bar', String(30), server_default='hi')
                 ),
                 {'id':'id1'},
                 {'id':'id1', 'bar':'hi'},
index 923c4f0e05073e06b2fa85a4497dbbe9d196d203..7c345ee6e0f58c95f1cc57e67b0f6b4b6424457e 100644 (file)
@@ -87,3 +87,12 @@ def deferrable_constraints(fn):
         unsupported('mysql', 'not supported by database'),
         unsupported('mssql', 'not supported by database'),
         )
+
+def row_triggers(fn):
+    """Target must support standard statement-running EACH ROW triggers."""
+    return _chain_decorators_on(
+        fn,
+        # no access to same table
+        exclude('mysql', '<', (5, 0, 10), 'not supported by database'),
+        unsupported('postgres', 'not supported by database: no statements'),
+        )
index da3e81d62b65395a4f1f4d19f2f2038e2fdbebb6..c31e591568e7d7740fa06cc52808913021ac4981 100644 (file)
@@ -684,9 +684,10 @@ class ComparesTables(object):
 
             self.assertEquals(set([f.column.name for f in c.foreign_keys]), set([f.column.name for f in reflected_c.foreign_keys]))
             if c.default:
-                assert isinstance(reflected_c.default, schema.PassiveDefault)
+                assert isinstance(reflected_c.server_default,
+                                  schema.FetchedValue)
             elif against(('mysql', '<', (5, 0))):
-                # ignore reflection of bogus db-generated PassiveDefault()
+                # ignore reflection of bogus db-generated DefaultClause()
                 pass
             elif not c.primary_key or not against('postgres'):
                 print repr(c)