mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-28 03:26:01 -04:00
c4d6596ba3
Implement rowcount assertions and single row check for post_update as well as deletes. Change-Id: I4e5ba7e8747bf0e0b41f569089eb8cdbf064b7a9 Fixes: #4062
645 lines
20 KiB
Python
645 lines
20 KiB
Python
from sqlalchemy import testing
|
|
from sqlalchemy.testing import eq_
|
|
from sqlalchemy.testing.schema import Table, Column
|
|
from sqlalchemy.testing import fixtures
|
|
from sqlalchemy import Integer, String, ForeignKey, FetchedValue
|
|
from sqlalchemy.orm import mapper, Session
|
|
from sqlalchemy.testing.assertsql import CompiledSQL
|
|
from test.orm import _fixtures
|
|
|
|
|
|
class BulkTest(testing.AssertsExecutionResults):
|
|
run_inserts = None
|
|
run_define_tables = 'each'
|
|
|
|
|
|
class BulkInsertUpdateVersionId(BulkTest, fixtures.MappedTest):
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
Table('version_table', metadata,
|
|
Column('id', Integer, primary_key=True,
|
|
test_needs_autoincrement=True),
|
|
Column('version_id', Integer, nullable=False),
|
|
Column('value', String(40), nullable=False))
|
|
|
|
@classmethod
|
|
def setup_classes(cls):
|
|
class Foo(cls.Comparable):
|
|
pass
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
Foo, version_table = cls.classes.Foo, cls.tables.version_table
|
|
|
|
mapper(Foo, version_table, version_id_col=version_table.c.version_id)
|
|
|
|
@testing.emits_warning(r".*versioning cannot be verified")
|
|
def test_bulk_insert_via_save(self):
|
|
Foo = self.classes.Foo
|
|
|
|
s = Session()
|
|
|
|
s.bulk_save_objects([Foo(value='value')])
|
|
|
|
eq_(
|
|
s.query(Foo).all(),
|
|
[Foo(version_id=1, value='value')]
|
|
)
|
|
|
|
@testing.emits_warning(r".*versioning cannot be verified")
|
|
def test_bulk_update_via_save(self):
|
|
Foo = self.classes.Foo
|
|
|
|
s = Session()
|
|
|
|
s.add(Foo(value='value'))
|
|
s.commit()
|
|
|
|
f1 = s.query(Foo).first()
|
|
f1.value = 'new value'
|
|
s.bulk_save_objects([f1])
|
|
s.expunge_all()
|
|
|
|
eq_(
|
|
s.query(Foo).all(),
|
|
[Foo(version_id=2, value='new value')]
|
|
)
|
|
|
|
|
|
class BulkInsertUpdateTest(BulkTest, _fixtures.FixtureTest):
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
User, Address, Order = cls.classes("User", "Address", "Order")
|
|
u, a, o = cls.tables("users", "addresses", "orders")
|
|
|
|
mapper(User, u)
|
|
mapper(Address, a)
|
|
mapper(Order, o)
|
|
|
|
def test_bulk_save_return_defaults(self):
|
|
User, = self.classes("User",)
|
|
|
|
s = Session()
|
|
objects = [
|
|
User(name="u1"),
|
|
User(name="u2"),
|
|
User(name="u3")
|
|
]
|
|
assert 'id' not in objects[0].__dict__
|
|
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_save_objects(objects, return_defaults=True)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO users (name) VALUES (:name)",
|
|
[{'name': 'u1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO users (name) VALUES (:name)",
|
|
[{'name': 'u2'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO users (name) VALUES (:name)",
|
|
[{'name': 'u3'}]
|
|
),
|
|
)
|
|
eq_(objects[0].__dict__['id'], 1)
|
|
|
|
def test_bulk_save_no_defaults(self):
|
|
User, = self.classes("User",)
|
|
|
|
s = Session()
|
|
objects = [
|
|
User(name="u1"),
|
|
User(name="u2"),
|
|
User(name="u3")
|
|
]
|
|
assert 'id' not in objects[0].__dict__
|
|
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_save_objects(objects)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO users (name) VALUES (:name)",
|
|
[{'name': 'u1'}, {'name': 'u2'}, {'name': 'u3'}]
|
|
),
|
|
)
|
|
assert 'id' not in objects[0].__dict__
|
|
|
|
def test_bulk_save_updated_include_unchanged(self):
|
|
User, = self.classes("User",)
|
|
|
|
s = Session(expire_on_commit=False)
|
|
objects = [
|
|
User(name="u1"),
|
|
User(name="u2"),
|
|
User(name="u3")
|
|
]
|
|
s.add_all(objects)
|
|
s.commit()
|
|
|
|
objects[0].name = 'u1new'
|
|
objects[2].name = 'u3new'
|
|
|
|
s = Session()
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_save_objects(objects, update_changed_only=False)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"UPDATE users SET name=:name WHERE "
|
|
"users.id = :users_id",
|
|
[{'users_id': 1, 'name': 'u1new'},
|
|
{'users_id': 2, 'name': 'u2'},
|
|
{'users_id': 3, 'name': 'u3new'}]
|
|
)
|
|
)
|
|
|
|
def test_bulk_update(self):
|
|
User, = self.classes("User",)
|
|
|
|
s = Session(expire_on_commit=False)
|
|
objects = [
|
|
User(name="u1"),
|
|
User(name="u2"),
|
|
User(name="u3")
|
|
]
|
|
s.add_all(objects)
|
|
s.commit()
|
|
|
|
s = Session()
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_update_mappings(
|
|
User,
|
|
[{'id': 1, 'name': 'u1new'},
|
|
{'id': 2, 'name': 'u2'},
|
|
{'id': 3, 'name': 'u3new'}]
|
|
)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"UPDATE users SET name=:name WHERE users.id = :users_id",
|
|
[{'users_id': 1, 'name': 'u1new'},
|
|
{'users_id': 2, 'name': 'u2'},
|
|
{'users_id': 3, 'name': 'u3new'}]
|
|
)
|
|
)
|
|
|
|
def test_bulk_insert(self):
|
|
User, = self.classes("User",)
|
|
|
|
s = Session()
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_insert_mappings(
|
|
User,
|
|
[{'id': 1, 'name': 'u1new'},
|
|
{'id': 2, 'name': 'u2'},
|
|
{'id': 3, 'name': 'u3new'}]
|
|
)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO users (id, name) VALUES (:id, :name)",
|
|
[{'id': 1, 'name': 'u1new'},
|
|
{'id': 2, 'name': 'u2'},
|
|
{'id': 3, 'name': 'u3new'}]
|
|
)
|
|
)
|
|
|
|
def test_bulk_insert_render_nulls(self):
|
|
Order, = self.classes("Order",)
|
|
|
|
s = Session()
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_insert_mappings(
|
|
Order,
|
|
[{'id': 1, 'description': 'u1new'},
|
|
{'id': 2, 'description': None},
|
|
{'id': 3, 'description': 'u3new'}],
|
|
render_nulls=True
|
|
)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO orders (id, description) "
|
|
"VALUES (:id, :description)",
|
|
[{'id': 1, 'description': 'u1new'},
|
|
{'id': 2, 'description': None},
|
|
{'id': 3, 'description': 'u3new'}]
|
|
)
|
|
)
|
|
|
|
|
|
class BulkUDPostfetchTest(BulkTest, fixtures.MappedTest):
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
Table(
|
|
'a', metadata,
|
|
Column(
|
|
'id', Integer,
|
|
primary_key=True,
|
|
test_needs_autoincrement=True),
|
|
Column('x', Integer),
|
|
Column('y', Integer,
|
|
server_default=FetchedValue(),
|
|
server_onupdate=FetchedValue()))
|
|
|
|
@classmethod
|
|
def setup_classes(cls):
|
|
class A(cls.Comparable):
|
|
pass
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
A = cls.classes.A
|
|
a = cls.tables.a
|
|
|
|
mapper(A, a)
|
|
|
|
def test_insert_w_fetch(self):
|
|
A = self.classes.A
|
|
|
|
s = Session()
|
|
a1 = A(x=1)
|
|
s.bulk_save_objects([a1])
|
|
s.commit()
|
|
|
|
def test_update_w_fetch(self):
|
|
A = self.classes.A
|
|
|
|
s = Session()
|
|
a1 = A(x=1, y=2)
|
|
s.add(a1)
|
|
s.commit()
|
|
|
|
eq_(a1.id, 1) # force a load
|
|
a1.x = 5
|
|
s.expire(a1, ['y'])
|
|
assert 'y' not in a1.__dict__
|
|
s.bulk_save_objects([a1])
|
|
s.commit()
|
|
|
|
eq_(a1.x, 5)
|
|
eq_(a1.y, 2)
|
|
|
|
|
|
class BulkUDTestAltColKeys(BulkTest, fixtures.MappedTest):
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
Table(
|
|
'people_keys', metadata,
|
|
Column(
|
|
'person_id', Integer,
|
|
primary_key=True, key='id'),
|
|
Column('name', String(50), key='personname'))
|
|
|
|
Table(
|
|
'people_attrs', metadata,
|
|
Column(
|
|
'person_id', Integer,
|
|
primary_key=True),
|
|
Column('name', String(50)))
|
|
|
|
Table(
|
|
'people_both', metadata,
|
|
Column(
|
|
'person_id', Integer,
|
|
primary_key=True, key="id_key"),
|
|
Column('name', String(50), key='name_key'))
|
|
|
|
@classmethod
|
|
def setup_classes(cls):
|
|
class PersonKeys(cls.Comparable):
|
|
pass
|
|
|
|
class PersonAttrs(cls.Comparable):
|
|
pass
|
|
|
|
class PersonBoth(cls.Comparable):
|
|
pass
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
PersonKeys, PersonAttrs, PersonBoth = cls.classes(
|
|
"PersonKeys", "PersonAttrs", "PersonBoth")
|
|
people_keys, people_attrs, people_both = cls.tables(
|
|
"people_keys", "people_attrs", "people_both")
|
|
|
|
mapper(PersonKeys, people_keys)
|
|
mapper(PersonAttrs, people_attrs, properties={
|
|
'id': people_attrs.c.person_id,
|
|
'personname': people_attrs.c.name
|
|
})
|
|
|
|
mapper(PersonBoth, people_both, properties={
|
|
'id': people_both.c.id_key,
|
|
'personname': people_both.c.name_key
|
|
})
|
|
|
|
def test_insert_keys(self):
|
|
self._test_insert(self.classes.PersonKeys)
|
|
|
|
def test_insert_attrs(self):
|
|
self._test_insert(self.classes.PersonAttrs)
|
|
|
|
def test_insert_both(self):
|
|
self._test_insert(self.classes.PersonBoth)
|
|
|
|
def test_update_keys(self):
|
|
self._test_update(self.classes.PersonKeys)
|
|
|
|
@testing.requires.updateable_autoincrement_pks
|
|
def test_update_attrs(self):
|
|
self._test_update(self.classes.PersonAttrs)
|
|
|
|
@testing.requires.updateable_autoincrement_pks
|
|
def test_update_both(self):
|
|
# want to make sure that before [ticket:3849], this did not have
|
|
# a successful behavior or workaround
|
|
self._test_update(self.classes.PersonBoth)
|
|
|
|
def _test_insert(self, person_cls):
|
|
Person = person_cls
|
|
|
|
s = Session()
|
|
s.bulk_insert_mappings(
|
|
Person, [{"id": 5, "personname": "thename"}]
|
|
)
|
|
|
|
eq_(
|
|
s.query(Person).first(),
|
|
Person(id=5, personname="thename")
|
|
)
|
|
|
|
def _test_update(self, person_cls):
|
|
Person = person_cls
|
|
|
|
s = Session()
|
|
s.add(Person(id=5, personname="thename"))
|
|
s.commit()
|
|
|
|
s.bulk_update_mappings(
|
|
Person, [{"id": 5, "personname": "newname"}]
|
|
)
|
|
|
|
eq_(
|
|
s.query(Person).first(),
|
|
Person(id=5, personname="newname")
|
|
)
|
|
|
|
|
|
class BulkInheritanceTest(BulkTest, fixtures.MappedTest):
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
Table(
|
|
'people', metadata,
|
|
Column(
|
|
'person_id', Integer,
|
|
primary_key=True,
|
|
test_needs_autoincrement=True),
|
|
Column('name', String(50)),
|
|
Column('type', String(30)))
|
|
|
|
Table(
|
|
'engineers', metadata,
|
|
Column(
|
|
'person_id', Integer,
|
|
ForeignKey('people.person_id'),
|
|
primary_key=True),
|
|
Column('status', String(30)),
|
|
Column('primary_language', String(50)))
|
|
|
|
Table(
|
|
'managers', metadata,
|
|
Column(
|
|
'person_id', Integer,
|
|
ForeignKey('people.person_id'),
|
|
primary_key=True),
|
|
Column('status', String(30)),
|
|
Column('manager_name', String(50)))
|
|
|
|
Table(
|
|
'boss', metadata,
|
|
Column(
|
|
'boss_id', Integer,
|
|
ForeignKey('managers.person_id'),
|
|
primary_key=True),
|
|
Column('golf_swing', String(30)))
|
|
|
|
@classmethod
|
|
def setup_classes(cls):
|
|
class Base(cls.Comparable):
|
|
pass
|
|
|
|
class Person(Base):
|
|
pass
|
|
|
|
class Engineer(Person):
|
|
pass
|
|
|
|
class Manager(Person):
|
|
pass
|
|
|
|
class Boss(Manager):
|
|
pass
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
Person, Engineer, Manager, Boss = \
|
|
cls.classes('Person', 'Engineer', 'Manager', 'Boss')
|
|
p, e, m, b = cls.tables('people', 'engineers', 'managers', 'boss')
|
|
|
|
mapper(
|
|
Person, p, polymorphic_on=p.c.type,
|
|
polymorphic_identity='person')
|
|
mapper(Engineer, e, inherits=Person, polymorphic_identity='engineer')
|
|
mapper(Manager, m, inherits=Person, polymorphic_identity='manager')
|
|
mapper(Boss, b, inherits=Manager, polymorphic_identity='boss')
|
|
|
|
def test_bulk_save_joined_inh_return_defaults(self):
|
|
Person, Engineer, Manager, Boss = \
|
|
self.classes('Person', 'Engineer', 'Manager', 'Boss')
|
|
|
|
s = Session()
|
|
objects = [
|
|
Manager(name='m1', status='s1', manager_name='mn1'),
|
|
Engineer(name='e1', status='s2', primary_language='l1'),
|
|
Engineer(name='e2', status='s3', primary_language='l2'),
|
|
Boss(
|
|
name='b1', status='s3', manager_name='mn2',
|
|
golf_swing='g1')
|
|
]
|
|
assert 'person_id' not in objects[0].__dict__
|
|
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_save_objects(objects, return_defaults=True)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO people (name, type) VALUES (:name, :type)",
|
|
[{'type': 'manager', 'name': 'm1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO managers (person_id, status, manager_name) "
|
|
"VALUES (:person_id, :status, :manager_name)",
|
|
[{'person_id': 1, 'status': 's1', 'manager_name': 'mn1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (name, type) VALUES (:name, :type)",
|
|
[{'type': 'engineer', 'name': 'e1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (name, type) VALUES (:name, :type)",
|
|
[{'type': 'engineer', 'name': 'e2'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO engineers (person_id, status, primary_language) "
|
|
"VALUES (:person_id, :status, :primary_language)",
|
|
[{'person_id': 2, 'status': 's2', 'primary_language': 'l1'},
|
|
{'person_id': 3, 'status': 's3', 'primary_language': 'l2'}]
|
|
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (name, type) VALUES (:name, :type)",
|
|
[{'type': 'boss', 'name': 'b1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO managers (person_id, status, manager_name) "
|
|
"VALUES (:person_id, :status, :manager_name)",
|
|
[{'person_id': 4, 'status': 's3', 'manager_name': 'mn2'}]
|
|
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO boss (boss_id, golf_swing) VALUES "
|
|
"(:boss_id, :golf_swing)",
|
|
[{'boss_id': 4, 'golf_swing': 'g1'}]
|
|
)
|
|
)
|
|
eq_(objects[0].__dict__['person_id'], 1)
|
|
eq_(objects[3].__dict__['person_id'], 4)
|
|
eq_(objects[3].__dict__['boss_id'], 4)
|
|
|
|
def test_bulk_save_joined_inh_no_defaults(self):
|
|
Person, Engineer, Manager, Boss = \
|
|
self.classes('Person', 'Engineer', 'Manager', 'Boss')
|
|
|
|
s = Session()
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_save_objects([
|
|
Manager(
|
|
person_id=1,
|
|
name='m1', status='s1', manager_name='mn1'),
|
|
Engineer(
|
|
person_id=2,
|
|
name='e1', status='s2', primary_language='l1'),
|
|
Engineer(
|
|
person_id=3,
|
|
name='e2', status='s3', primary_language='l2'),
|
|
Boss(
|
|
person_id=4, boss_id=4,
|
|
name='b1', status='s3', manager_name='mn2',
|
|
golf_swing='g1')
|
|
],
|
|
|
|
)
|
|
|
|
# the only difference here is that common classes are grouped together.
|
|
# at the moment it doesn't lump all the "people" tables from
|
|
# different classes together.
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO people (person_id, name, type) VALUES "
|
|
"(:person_id, :name, :type)",
|
|
[{'person_id': 1, 'type': 'manager', 'name': 'm1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO managers (person_id, status, manager_name) "
|
|
"VALUES (:person_id, :status, :manager_name)",
|
|
[{'status': 's1', 'person_id': 1, 'manager_name': 'mn1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (person_id, name, type) VALUES "
|
|
"(:person_id, :name, :type)",
|
|
[{'person_id': 2, 'type': 'engineer', 'name': 'e1'},
|
|
{'person_id': 3, 'type': 'engineer', 'name': 'e2'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO engineers (person_id, status, primary_language) "
|
|
"VALUES (:person_id, :status, :primary_language)",
|
|
[{'person_id': 2, 'status': 's2', 'primary_language': 'l1'},
|
|
{'person_id': 3, 'status': 's3', 'primary_language': 'l2'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (person_id, name, type) VALUES "
|
|
"(:person_id, :name, :type)",
|
|
[{'person_id': 4, 'type': 'boss', 'name': 'b1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO managers (person_id, status, manager_name) "
|
|
"VALUES (:person_id, :status, :manager_name)",
|
|
[{'status': 's3', 'person_id': 4, 'manager_name': 'mn2'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO boss (boss_id, golf_swing) VALUES "
|
|
"(:boss_id, :golf_swing)",
|
|
[{'boss_id': 4, 'golf_swing': 'g1'}]
|
|
)
|
|
)
|
|
|
|
def test_bulk_insert_joined_inh_return_defaults(self):
|
|
Person, Engineer, Manager, Boss = \
|
|
self.classes('Person', 'Engineer', 'Manager', 'Boss')
|
|
|
|
s = Session()
|
|
with self.sql_execution_asserter() as asserter:
|
|
s.bulk_insert_mappings(
|
|
Boss,
|
|
[
|
|
dict(
|
|
name='b1', status='s1', manager_name='mn1',
|
|
golf_swing='g1'
|
|
),
|
|
dict(
|
|
name='b2', status='s2', manager_name='mn2',
|
|
golf_swing='g2'
|
|
),
|
|
dict(
|
|
name='b3', status='s3', manager_name='mn3',
|
|
golf_swing='g3'
|
|
),
|
|
], return_defaults=True
|
|
)
|
|
|
|
asserter.assert_(
|
|
CompiledSQL(
|
|
"INSERT INTO people (name) VALUES (:name)",
|
|
[{'name': 'b1'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (name) VALUES (:name)",
|
|
[{'name': 'b2'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO people (name) VALUES (:name)",
|
|
[{'name': 'b3'}]
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO managers (person_id, status, manager_name) "
|
|
"VALUES (:person_id, :status, :manager_name)",
|
|
[{'person_id': 1, 'status': 's1', 'manager_name': 'mn1'},
|
|
{'person_id': 2, 'status': 's2', 'manager_name': 'mn2'},
|
|
{'person_id': 3, 'status': 's3', 'manager_name': 'mn3'}]
|
|
|
|
),
|
|
CompiledSQL(
|
|
"INSERT INTO boss (boss_id, golf_swing) VALUES "
|
|
"(:boss_id, :golf_swing)",
|
|
[{'golf_swing': 'g1', 'boss_id': 1},
|
|
{'golf_swing': 'g2', 'boss_id': 2},
|
|
{'golf_swing': 'g3', 'boss_id': 3}]
|
|
)
|
|
)
|