]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Check row for None with implicit returning PK to accommodate ON CONFLICT
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 4 Oct 2016 20:46:08 +0000 (16:46 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 4 Oct 2016 21:31:40 +0000 (17:31 -0400)
An adjustment to ON CONFLICT such that the "inserted_primary_key"
logic is able to accommodate the case where there's no INSERT or
UPDATE and there's no net change.  The value comes out as None
in this case, rather than failing on an exception.

Change-Id: I0794e95c3ca262cb1ab2387167d96b8984225fce
Fixes: #3813
doc/build/changelog/changelog_11.rst
lib/sqlalchemy/engine/default.py
test/dialect/postgresql/test_on_conflict.py

index c5be9c7c257186021421bb234a6e38daf53730ef..afdade444b269153528cb9d7d52b5f24c6179bf5 100644 (file)
         lazy load would actually emit SQL, vs. raising if the lazy loader
         mechanism is invoked at all.
 
+    .. change::
+        :tags: bug, postgresql
+        :tickets: 3813
+
+        An adjustment to ON CONFLICT such that the "inserted_primary_key"
+        logic is able to accommodate the case where there's no INSERT or
+        UPDATE and there's no net change.  The value comes out as None
+        in this case, rather than failing on an exception.
+
     .. change::
         :tags: bug, orm
         :tickets: 3811
index 733a890769ab966fe06b98730dc3d34616be5221..891103ee0f98e22ab4fad5c9c98ca81fbe0b34df 100644 (file)
@@ -502,6 +502,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
     compiled = None
     statement = None
     result_column_struct = None
+    returned_defaults = None
     _is_implicit_returning = False
     _is_explicit_returning = False
 
@@ -917,10 +918,13 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
         ]
 
     def _setup_ins_pk_from_implicit_returning(self, row):
+        if row is None:
+            self.inserted_primary_key = None
+            return
+
         key_getter = self.compiled._key_getters_for_crud_column[2]
         table = self.compiled.statement.table
         compiled_params = self.compiled_parameters[0]
-
         self.inserted_primary_key = [
             row[col] if value is None else value
             for col, value in [
index 201287d628f54e0f59b8ac5c6e0fdbc78db3a379..eb0298bc27c69464e00301d4dca42473250700ff 100644 (file)
@@ -59,14 +59,20 @@ class OnConflictTest(fixtures.TablesTest):
         users = self.tables.users
 
         with testing.db.connect() as conn:
-            conn.execute(
+            result = conn.execute(
                 insert(users).on_conflict_do_nothing(),
                 dict(id=1, name='name1')
             )
-            conn.execute(
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
+
+            result = conn.execute(
                 insert(users).on_conflict_do_nothing(),
                 dict(id=1, name='name2')
             )
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
+
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
                 [(1, 'name1')]
@@ -77,18 +83,24 @@ class OnConflictTest(fixtures.TablesTest):
         users = self.tables.users
 
         with testing.db.connect() as conn:
-            conn.execute(
+            result = conn.execute(
                 insert(users)
                 .on_conflict_do_nothing(
                     index_elements=users.primary_key.columns),
                 dict(id=1, name='name1')
             )
-            conn.execute(
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
+
+            result = conn.execute(
                 insert(users)
                 .on_conflict_do_nothing(
                     index_elements=users.primary_key.columns),
                 dict(id=1, name='name2')
             )
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
+
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
                 [(1, 'name1')]
@@ -104,7 +116,10 @@ class OnConflictTest(fixtures.TablesTest):
             i = i.on_conflict_do_update(
                 index_elements=[users.c.id],
                 set_=dict(name=i.excluded.name))
-            conn.execute(i, dict(id=1, name='name1'))
+            result = conn.execute(i, dict(id=1, name='name1'))
+
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
@@ -123,7 +138,10 @@ class OnConflictTest(fixtures.TablesTest):
                 set_=dict(id=i.excluded.id, name=i.excluded.name)
             )
 
-            conn.execute(i, dict(id=1, name='name2'))
+            result = conn.execute(i, dict(id=1, name='name2'))
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
+
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
                 [(1, 'name2')]
@@ -140,7 +158,9 @@ class OnConflictTest(fixtures.TablesTest):
                 index_elements=users.primary_key.columns,
                 set_=dict(name=i.excluded.name)
             )
-            conn.execute(i, dict(id=1, name='name3'))
+            result = conn.execute(i, dict(id=1, name='name3'))
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
@@ -159,7 +179,9 @@ class OnConflictTest(fixtures.TablesTest):
                 set_=dict(id=i.excluded.id, name=i.excluded.name)
             ).values(id=1, name='name4')
 
-            conn.execute(i)
+            result = conn.execute(i)
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
@@ -178,7 +200,9 @@ class OnConflictTest(fixtures.TablesTest):
                 set_=dict(id=10, name="I'm a name")
             ).values(id=1, name='name4')
 
-            conn.execute(i)
+            result = conn.execute(i)
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(
@@ -222,10 +246,12 @@ class OnConflictTest(fixtures.TablesTest):
                     name=i.excluded.name,
                     login_email=i.excluded.login_email)
             )
-            conn.execute(i, dict(
+            result = conn.execute(i, dict(
                 id=1, name='name2', login_email='name1@gmail.com',
                 lets_index_this='not')
             )
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(users.select().where(users.c.id == 1)).fetchall(),
@@ -246,10 +272,12 @@ class OnConflictTest(fixtures.TablesTest):
                           login_email=i.excluded.login_email)
             )
             # note: lets_index_this value totally ignored in SET clause.
-            conn.execute(i, dict(
+            result = conn.execute(i, dict(
                 id=42, name='nameunique',
                 login_email='name2@gmail.com', lets_index_this='unique')
             )
+            eq_(result.inserted_primary_key, [42])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(
@@ -275,10 +303,12 @@ class OnConflictTest(fixtures.TablesTest):
             )
             # note: lets_index_this value totally ignored in SET clause.
 
-            conn.execute(i, dict(
+            result = conn.execute(i, dict(
                 id=43, name='nameunique2',
                 login_email='name2@gmail.com', lets_index_this='unique')
             )
+            eq_(result.inserted_primary_key, [43])
+            eq_(result.returned_defaults, None)
 
             eq_(
                 conn.execute(
@@ -288,6 +318,36 @@ class OnConflictTest(fixtures.TablesTest):
                 [(43, 'nameunique2', 'name2@gmail.com', 'not')]
             )
 
+    def test_on_conflict_do_update_exotic_targets_four_no_pk(self):
+        users = self.tables.users_xtra
+
+        with testing.db.connect() as conn:
+            self._exotic_targets_fixture(conn)
+            # try unique constraint by name: cause an
+            # upsert on target login_email, not id
+            i = insert(users)
+            i = i.on_conflict_do_update(
+                index_elements=[users.c.login_email],
+                set_=dict(
+                    id=i.excluded.id, name=i.excluded.name,
+                    login_email=i.excluded.login_email)
+            )
+
+            result = conn.execute(i, dict(
+                name='name3',
+                login_email='name1@gmail.com')
+            )
+            eq_(result.inserted_primary_key, [1])
+            eq_(result.returned_defaults, (1, ))
+
+            eq_(
+                conn.execute(users.select().order_by(users.c.id)).fetchall(),
+                [
+                    (1, 'name3', 'name1@gmail.com', 'not'),
+                    (2, 'name2', 'name2@gmail.com', 'not')
+                ]
+            )
+
     def test_on_conflict_do_update_exotic_targets_five(self):
         users = self.tables.users_xtra
 
@@ -310,3 +370,28 @@ class OnConflictTest(fixtures.TablesTest):
                     id=1, name='namebogus', login_email='bogus@gmail.com',
                     lets_index_this='bogus')
             )
+
+    def test_on_conflict_do_update_no_row_actually_affected(self):
+        users = self.tables.users_xtra
+
+        with testing.db.connect() as conn:
+            self._exotic_targets_fixture(conn)
+            i = insert(users)
+            i = i.on_conflict_do_update(
+                index_elements=[users.c.login_email],
+                set_=dict(name='new_name'),
+                where=(i.excluded.name == 'other_name')
+            )
+            result = conn.execute(
+                i, dict(name='name2', login_email='name1@gmail.com'))
+
+            eq_(result.returned_defaults, None)
+            eq_(result.inserted_primary_key, None)
+
+            eq_(
+                conn.execute(users.select()).fetchall(),
+                [
+                    (1, 'name1', 'name1@gmail.com', 'not'),
+                    (2, 'name2', 'name2@gmail.com', 'not')
+                ]
+            )