From: Mike Bayer Date: Mon, 31 Aug 2009 20:38:14 +0000 (+0000) Subject: - Inserting NULL into a primary key + foreign key column X-Git-Tag: rel_0_6beta1~312 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3d38969fd4f88d996d438df63f7cbb1833b63429;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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.) --- diff --git a/CHANGES b/CHANGES index fad467e8ac..8682440d53 100644 --- 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() diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 321601a838..7f9cc889a9 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -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): diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 973bacd068..a09697e790 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -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 \ diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 935d1e087d..78164e459c 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -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 diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 78529ee6dd..7bd0c1b05d 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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) diff --git a/test/dialect/test_postgresql.py b/test/dialect/test_postgresql.py index e1c351a93e..c27e24e43a 100644 --- a/test/dialect/test_postgresql.py +++ b/test/dialect/test_postgresql.py @@ -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), diff --git a/test/orm/inheritance/test_query.py b/test/orm/inheritance/test_query.py index daf8bf3bd0..ee2d7f0e5f 100644 --- a/test/orm/inheritance/test_query.py +++ b/test/orm/inheritance/test_query.py @@ -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')] )