]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Inserting NULL into a primary key + foreign key column
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 31 Aug 2009 20:38:14 +0000 (20:38 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 31 Aug 2009 20:38:14 +0000 (20:38 +0000)
will allow the "not null constraint" error to raise,
not an attempt to execute a nonexistent "col_id_seq"
sequence.  [ticket:1516]

- autoincrement SELECT statements, i.e. those which
select from a procedure that modifies rows, now work
with server-side cursor mode (the named cursor isn't
used for such statements.)

CHANGES
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/dialects/postgresql/psycopg2.py
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/sql/compiler.py
test/dialect/test_postgresql.py
test/orm/inheritance/test_query.py

diff --git a/CHANGES b/CHANGES
index fad467e8acba66c265b71aa93fe3f45009c4953c..8682440d534e1be0597f7b55d8de8dd4ed79ac14 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -235,6 +235,16 @@ CHANGES
       just to treat that one non-existent use case.
       [ticket:1279]
     
+    - Inserting NULL into a primary key + foreign key column
+      will allow the "not null constraint" error to raise,
+      not an attempt to execute a nonexistent "col_id_seq" 
+      sequence.  [ticket:1516]
+    
+    - autoincrement SELECT statements, i.e. those which 
+      select from a procedure that modifies rows, now work 
+      with server-side cursor mode (the named cursor isn't 
+      used for such statements.)
+      
 - mysql
     - all the _detect_XXX() functions now run once underneath
       dialect.initialize()
index 321601a838ef6a8162ed26e84261bd9addd79710..7f9cc889a91b433ad701cea329cc9b77bceb7a78 100644 (file)
@@ -333,32 +333,29 @@ class PGDDLCompiler(compiler.DDLCompiler):
 
 
 class PGDefaultRunner(base.DefaultRunner):
-    def __init__(self, context):
-        base.DefaultRunner.__init__(self, context)
-        # craete cursor which won't conflict with a server-side cursor
-        self.cursor = context._connection.connection.cursor()
-    
+
     def get_column_default(self, column, isinsert=True):
         if column.primary_key:
-            # pre-execute passive defaults on primary keys
             if (isinstance(column.server_default, schema.DefaultClause) and
                 column.server_default.arg is not None):
+
+                # pre-execute passive defaults on primary key columns
                 return self.execute_string("select %s" % column.server_default.arg)
-            elif (isinstance(column.type, sqltypes.Integer) and column.autoincrement) \
+
+            elif column is column.table._autoincrement_column \
                     and (column.default is None or (isinstance(column.default, schema.Sequence) and column.default.optional)):
+
+                # execute the sequence associated with a SERIAL primary key column.  
+                # for non-primary-key SERIAL, the ID just generates server side.
                 sch = column.table.schema
-                # TODO: this has to build into the Sequence object so we can get the quoting
-                # logic from it
+
                 if sch is not None:
                     exc = "select nextval('\"%s\".\"%s_%s_seq\"')" % (sch, column.table.name, column.name)
                 else:
                     exc = "select nextval('\"%s_%s_seq\"')" % (column.table.name, column.name)
 
-                if self.dialect.supports_unicode_statements:
-                    return self.execute_string(exc)
-                else:
-                    return self.execute_string(exc.encode(self.dialect.encoding))
-
+                return self.execute_string(exc)
+        
         return super(PGDefaultRunner, self).get_column_default(column)
 
     def visit_sequence(self, seq):
index 973bacd06840dda09bfb80d9b5e29aa414326563..a09697e79061952db281eef0bb72eb6e90d98b37 100644 (file)
@@ -70,6 +70,7 @@ class PostgreSQL_psycopg2ExecutionContext(default.DefaultExecutionContext):
         # TODO: coverage for server side cursors + select.for_update()
         is_server_side = \
             self.dialect.server_side_cursors and \
+            not self.should_autocommit and \
             ((self.compiled and isinstance(self.compiled.statement, expression.Selectable) 
                 and not getattr(self.compiled.statement, 'for_update', False)) \
             or \
index 935d1e087d486303452c8a002ce5aff389076b9f..78164e459ce5dccede14ed2db99ebfc994f54aa0 100644 (file)
@@ -254,8 +254,8 @@ class DefaultExecutionContext(base.ExecutionContext):
             else:
                 self.statement = statement
             self.isinsert = self.isupdate = self.isdelete = False
-            self.cursor = self.create_cursor()
             self.should_autocommit = self.should_autocommit_text(statement)
+            self.cursor = self.create_cursor()
         else:
             # no statement. used for standalone ColumnDefault execution.
             self.statement = self.compiled = None
index 78529ee6ddc7686fb785b126a7176531e716d4bf..7bd0c1b05da1260aae5c3f12bde73bb961b4aedd 100644 (file)
@@ -844,8 +844,7 @@ class SQLCompiler(engine.Compiled):
                                         self.dialect.supports_sequences or 
                                         not isinstance(c.default, schema.Sequence)
                                     )
-                                ) or \
-                                self.dialect.preexecute_autoincrement_sequences:
+                                ) or self.dialect.preexecute_autoincrement_sequences:
 
                                 values.append((c, self._create_crud_bind_param(c, None)))
                                 self.prefetch.append(c)
index e1c351a93ec50b4553b6ff802c1717cc262ef6c6..c27e24e43afdb004becc2acf24f040786dd9c185 100644 (file)
@@ -131,6 +131,28 @@ class InsertTest(TestBase, AssertsExecutionResults):
         ins.execute({'x':"five"}, {'x':"seven"})
         assert table.select().execute().fetchall() == [(1, 'five'), (2, 'seven')]
 
+    def test_foreignkey_missing_insert(self):
+        t1 = Table('t1', metadata,
+            Column('id', Integer, primary_key=True)
+        )
+        t2 = Table('t2', metadata,
+            Column('id', Integer, ForeignKey('t1.id'), primary_key=True)
+        )
+        metadata.create_all()
+        
+        # want to ensure that 
+        # "null value in column "id" violates not-null constraint" is raised (IntegrityError on psycoopg2,
+        # but ProgrammingError on pg8000),
+        # and not "ProgrammingError: (ProgrammingError) relation "t2_id_seq" does not exist".
+        # the latter corresponds to autoincrement behavior, which is not the case
+        # here due to the foreign key.
+        for eng in [
+            engines.testing_engine(options={'implicit_returning':False}),
+            engines.testing_engine(options={'implicit_returning':True}),
+        ]:
+            assert_raises_message(exc.DBAPIError, "violates not-null constraint", eng.execute, t2.insert())
+        
+        
     def test_sequence_insert(self):
         table = Table('testtable', metadata,
             Column('id', Integer, Sequence('my_seq'), primary_key=True),
index daf8bf3bd03d30f984d6de73a7cb024f8b905633..ee2d7f0e5fc1403518521155fc07affc69a9b8a3 100644 (file)
@@ -367,7 +367,10 @@ def _produce_test(select_type):
             )
             
             eq_(
-                sess.query(Manager.name, Paperwork.description).join((Paperwork, Manager.person_id==Paperwork.person_id)).all(),
+                sess.query(Manager.name, Paperwork.description).
+                    join((Paperwork, Manager.person_id==Paperwork.person_id)).
+                    order_by(Paperwork.paperwork_id).
+                    all(),
                 [(u'pointy haired boss', u'review #1'), (u'dogbert', u'review #2'), (u'dogbert', u'review #3')]
             )