From: Mike Bayer Date: Fri, 7 Oct 2016 13:05:34 +0000 (-0400) Subject: Change autoincrement compileerror to a warning X-Git-Tag: rel_1_1_1~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8a13957db790c77b76c11f5f43fad1492a50fcf0;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Change autoincrement compileerror to a warning Users are complaining that IntegrityError is no longer raised. Change-Id: I0855d5b7a98d4338f0910501b6e6d404ba33634d Fixes: #3216 --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 4b4c5204be..20f006eeb5 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -31,6 +31,18 @@ was anticipated, the exception catch was not broad enough so it now catches all forms of pyodbc.Error. + .. change:: + :tags: bug, core + :tickets: 3216 + + Changed the CompileError raised when various primary key missing + situations are detected to a warning. The statement is again + passed to the database where it will fail and the DBAPI error (usually + IntegrityError) raises as usual. + + .. seealso:: + + :ref:`change_3216` .. changelog:: :version: 1.1.0 diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst index 560872fc92..3485b0e83c 100644 --- a/doc/build/changelog/migration_11.rst +++ b/doc/build/changelog/migration_11.rst @@ -1299,9 +1299,9 @@ have autoincrement set up; given a table such as:: Column('y', Integer, primary_key=True) ) -An INSERT emitted with no values for this table will produce the exception:: +An INSERT emitted with no values for this table will produce this warning:: - CompileError: Column 'b.x' is marked as a member of the primary + SAWarning: Column 'b.x' is marked as a member of the primary key for table 'b', but has no Python-side or server-side default generator indicated, nor does it indicate 'autoincrement=True', and no explicit value is passed. Primary key columns may not diff --git a/lib/sqlalchemy/sql/crud.py b/lib/sqlalchemy/sql/crud.py index f770fc5134..642659c84f 100644 --- a/lib/sqlalchemy/sql/crud.py +++ b/lib/sqlalchemy/sql/crud.py @@ -301,7 +301,7 @@ def _scan_cols( elif c.primary_key and \ c is not stmt.table._autoincrement_column and \ not c.nullable: - _raise_pk_with_no_anticipated_value(c) + _warn_pk_with_no_anticipated_value(c) elif compiler.isupdate: _append_param_update( @@ -379,7 +379,7 @@ def _append_param_insert_pk_returning(compiler, stmt, c, values, kw): elif not c.nullable: # no .default, no .server_default, not autoincrement, we have # no indication this primary key column will have any value - _raise_pk_with_no_anticipated_value(c) + _warn_pk_with_no_anticipated_value(c) def _create_insert_prefetch_bind_param(compiler, c, process=True, name=None): @@ -464,7 +464,7 @@ def _append_param_insert_pk(compiler, stmt, c, values, kw): elif c.default is None and c.server_default is None and not c.nullable: # no .default, no .server_default, not autoincrement, we have # no indication this primary key column will have any value - _raise_pk_with_no_anticipated_value(c) + _warn_pk_with_no_anticipated_value(c) def _append_param_insert_hasdefault( @@ -671,7 +671,7 @@ def _get_returning_modifiers(compiler, stmt): implicit_return_defaults, postfetch_lastrowid -def _raise_pk_with_no_anticipated_value(c): +def _warn_pk_with_no_anticipated_value(c): msg = ( "Column '%s.%s' is marked as a member of the " "primary key for table '%s', " @@ -689,4 +689,4 @@ def _raise_pk_with_no_anticipated_value(c): "behavior is expected for one of the columns in the primary key. " "CREATE TABLE statements are impacted by this change as well on " "most backends.") - raise exc.CompileError(msg) + util.warn(msg) diff --git a/test/dialect/postgresql/test_query.py b/test/dialect/postgresql/test_query.py index 538312a6a9..b8129f1e36 100644 --- a/test/dialect/postgresql/test_query.py +++ b/test/dialect/postgresql/test_query.py @@ -1,7 +1,7 @@ # coding: utf-8 from sqlalchemy.testing import AssertsExecutionResults, eq_, \ - assert_raises_message, AssertsCompiledSQL + assert_raises_message, AssertsCompiledSQL, expect_warnings, assert_raises from sqlalchemy import Table, Column, MetaData, Integer, String, bindparam, \ Sequence, ForeignKey, text, select, func, extract, literal_column, \ tuple_, DateTime, Time, literal, and_, Date, or_ @@ -69,11 +69,13 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults): engines.testing_engine(options={'implicit_returning': False}), engines.testing_engine(options={'implicit_returning': True}) ]: - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - eng.execute, t2.insert() - ) + with expect_warnings( + ".*has no Python-side or server-side default.*" + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + eng.execute, t2.insert() + ) def test_sequence_insert(self): table = Table( @@ -523,24 +525,32 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults): with engine.connect() as conn: conn.execute(table.insert(), {'id': 30, 'data': 'd1'}) - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - conn.execute, table.insert(), {'data': 'd2'}) - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - conn.execute, table.insert(), {'data': 'd2'}, - {'data': 'd3'}) - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - conn.execute, table.insert(), {'data': 'd2'}) - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - conn.execute, table.insert(), {'data': 'd2'}, - {'data': 'd3'}) + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + conn.execute, table.insert(), {'data': 'd2'}) + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + conn.execute, table.insert(), {'data': 'd2'}, + {'data': 'd3'}) + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + conn.execute, table.insert(), {'data': 'd2'}) + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + conn.execute, table.insert(), {'data': 'd2'}, + {'data': 'd3'}) conn.execute( table.insert(), @@ -560,15 +570,20 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults): table = Table(table.name, m2, autoload=True) with engine.connect() as conn: conn.execute(table.insert(), {'id': 30, 'data': 'd1'}) - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - conn.execute, table.insert(), {'data': 'd2'}) - assert_raises_message( - exc.CompileError, - ".*has no Python-side or server-side default.*", - conn.execute, table.insert(), {'data': 'd2'}, - {'data': 'd3'}) + + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + conn.execute, table.insert(), {'data': 'd2'}) + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + assert_raises( + (exc.IntegrityError, exc.ProgrammingError), + conn.execute, table.insert(), {'data': 'd2'}, + {'data': 'd3'}) conn.execute( table.insert(), {'id': 31, 'data': 'd2'}, {'id': 32, 'data': 'd3'}) diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py index 473f4f4625..1ac67bd319 100644 --- a/test/dialect/test_sqlite.py +++ b/test/dialect/test_sqlite.py @@ -5,7 +5,7 @@ import os import datetime from sqlalchemy.testing import eq_, assert_raises, \ - assert_raises_message, is_ + assert_raises_message, is_, expect_warnings from sqlalchemy import Table, select, bindparam, Column,\ MetaData, func, extract, ForeignKey, text, DefaultClause, and_, \ create_engine, UniqueConstraint, Index, PrimaryKeyConstraint @@ -803,13 +803,20 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults): @testing.exclude('sqlite', '<', (3, 3, 8), 'no database support') def test_empty_insert_pk2(self): - # now raises CompileError due to [ticket:3216] - assert_raises( - exc.CompileError, self._test_empty_insert, - Table( - 'b', MetaData(testing.db), - Column('x', Integer, primary_key=True), - Column('y', Integer, primary_key=True))) + # now warns due to [ticket:3216] + + with expect_warnings( + "Column 'b.x' is marked as a member of the " + "primary key for table 'b'", + "Column 'b.y' is marked as a member of the " + "primary key for table 'b'", + ): + assert_raises( + exc.IntegrityError, self._test_empty_insert, + Table( + 'b', MetaData(testing.db), + Column('x', Integer, primary_key=True), + Column('y', Integer, primary_key=True))) @testing.exclude('sqlite', '<', (3, 3, 8), 'no database support') def test_empty_insert_pk2_fv(self): @@ -824,13 +831,19 @@ class InsertTest(fixtures.TestBase, AssertsExecutionResults): @testing.exclude('sqlite', '<', (3, 3, 8), 'no database support') def test_empty_insert_pk3(self): - # now raises CompileError due to [ticket:3216] - assert_raises( - exc.CompileError, self._test_empty_insert, - Table( - 'c', MetaData(testing.db), - Column('x', Integer, primary_key=True), - Column('y', Integer, DefaultClause('123'), primary_key=True))) + # now warns due to [ticket:3216] + with expect_warnings( + "Column 'c.x' is marked as a member of the primary key for table" + ): + assert_raises( + exc.IntegrityError, + self._test_empty_insert, + Table( + 'c', MetaData(testing.db), + Column('x', Integer, primary_key=True), + Column('y', Integer, + DefaultClause('123'), primary_key=True)) + ) @testing.exclude('sqlite', '<', (3, 3, 8), 'no database support') def test_empty_insert_pk3_fv(self): diff --git a/test/sql/test_defaults.py b/test/sql/test_defaults.py index 1b033fce8f..b294158f45 100644 --- a/test/sql/test_defaults.py +++ b/test/sql/test_defaults.py @@ -1,5 +1,5 @@ from sqlalchemy.testing import eq_, assert_raises_message, \ - assert_raises, AssertsCompiledSQL + assert_raises, AssertsCompiledSQL, expect_warnings import datetime from sqlalchemy.schema import CreateSequence, DropSequence, CreateTable from sqlalchemy.sql import select, text, literal_column @@ -848,6 +848,7 @@ class AutoIncrementTest(fixtures.TablesTest): ) assert x._autoincrement_column is None + @testing.only_on("sqlite") def test_non_autoincrement(self): # sqlite INT primary keys can be non-unique! (only for ints) nonai = Table( @@ -861,13 +862,12 @@ class AutoIncrementTest(fixtures.TablesTest): # mysql in legacy mode fails on second row nonai.insert().execute(data='row 1') nonai.insert().execute(data='row 2') - assert_raises_message( - sa.exc.CompileError, - ".*has no Python-side or server-side default.*", - go - ) - nonai.insert().execute(id=1, data='row 1') + # just testing SQLite for now, it passes + with expect_warnings( + ".*has no Python-side or server-side default.*", + ): + go() def test_col_w_sequence_non_autoinc_no_firing(self): metadata = self.metadata diff --git a/test/sql/test_insert.py b/test/sql/test_insert.py index f2515c4eba..3a884643be 100644 --- a/test/sql/test_insert.py +++ b/test/sql/test_insert.py @@ -5,7 +5,7 @@ from sqlalchemy import Column, Integer, MetaData, String, Table,\ from sqlalchemy.dialects import mysql, postgresql from sqlalchemy.engine import default from sqlalchemy.testing import AssertsCompiledSQL,\ - assert_raises_message, fixtures, eq_ + assert_raises_message, fixtures, eq_, expect_warnings from sqlalchemy.sql import crud class _InsertTestBase(object): @@ -484,13 +484,16 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): 't', MetaData(), Column('x', Integer, primary_key=True), Column('y', Integer, primary_key=True) ) - assert_raises_message( - exc.CompileError, + + with expect_warnings( "Column 't.y' is marked as a member.*" "Note that as of SQLAlchemy 1.1,", - t.insert().compile, column_keys=['x'] - - ) + ): + self.assert_compile( + t.insert(), + "INSERT INTO t (x) VALUES (:x)", + params={'x': 5}, + ) def test_anticipate_no_pk_composite_pk_implicit_returning(self): t = Table( @@ -499,13 +502,17 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): ) d = postgresql.dialect() d.implicit_returning = True - assert_raises_message( - exc.CompileError, + + with expect_warnings( "Column 't.y' is marked as a member.*" "Note that as of SQLAlchemy 1.1,", - t.insert().compile, dialect=d, column_keys=['x'] - - ) + ): + self.assert_compile( + t.insert(), + "INSERT INTO t (x) VALUES (%(x)s)", + params={"x": 5}, + dialect=d + ) def test_anticipate_no_pk_composite_pk_prefetch(self): t = Table( @@ -514,13 +521,16 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): ) d = postgresql.dialect() d.implicit_returning = False - assert_raises_message( - exc.CompileError, + with expect_warnings( "Column 't.y' is marked as a member.*" - "Note that as of SQLAlchemy 1.1,", - t.insert().compile, dialect=d, column_keys=['x'] - - ) + "Note that as of SQLAlchemy 1.1," + ): + self.assert_compile( + t.insert(), + "INSERT INTO t (x) VALUES (%(x)s)", + params={'x': 5}, + dialect=d + ) def test_anticipate_nullable_composite_pk(self): t = Table( @@ -539,13 +549,15 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): Column('x', Integer, primary_key=True, autoincrement=False), Column('q', Integer) ) - assert_raises_message( - exc.CompileError, + with expect_warnings( "Column 't.x' is marked as a member.*" - "may not store NULL.$", - t.insert().compile, column_keys=['q'] - - ) + "may not store NULL.$" + ): + self.assert_compile( + t.insert(), + "INSERT INTO t (q) VALUES (:q)", + params={"q": 5} + ) def test_anticipate_no_pk_non_composite_pk_implicit_returning(self): t = Table( @@ -555,13 +567,16 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): ) d = postgresql.dialect() d.implicit_returning = True - assert_raises_message( - exc.CompileError, + with expect_warnings( "Column 't.x' is marked as a member.*" "may not store NULL.$", - t.insert().compile, dialect=d, column_keys=['q'] - - ) + ): + self.assert_compile( + t.insert(), + "INSERT INTO t (q) VALUES (%(q)s)", + params={"q": 5}, + dialect=d + ) def test_anticipate_no_pk_non_composite_pk_prefetch(self): t = Table( @@ -571,13 +586,17 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL): ) d = postgresql.dialect() d.implicit_returning = False - assert_raises_message( - exc.CompileError, - "Column 't.x' is marked as a member.*" - "may not store NULL.$", - t.insert().compile, dialect=d, column_keys=['q'] - ) + with expect_warnings( + "Column 't.x' is marked as a member.*" + "may not store NULL.$" + ): + self.assert_compile( + t.insert(), + "INSERT INTO t (q) VALUES (%(q)s)", + params={"q": 5}, + dialect=d + ) class InsertImplicitReturningTest(