diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-06-06 20:40:43 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-06-10 15:29:01 -0400 |
commit | b0cfa7379cf8513a821a3dbe3028c4965d9f85bd (patch) | |
tree | 19a79632b4f159092d955765ff9f7e842808bce7 /doc/build/orm/tutorial.rst | |
parent | 3ab2364e78641c4f0e4b6456afc2cbed39b0d0e6 (diff) | |
download | sqlalchemy-b0cfa7379cf8513a821a3dbe3028c4965d9f85bd.tar.gz |
Turn on caching everywhere, add logging
A variety of caching issues found by running
all tests with statement caching turned on.
The cache system now has a more conservative approach where
any subclass of a SQL element will by default invalidate
the cache key unless it adds the flag inherit_cache=True
at the class level, or if it implements its own caching.
Add working caching to a few elements that were
omitted previously; fix some caching implementations
to suit lesser used edge cases such as json casts
and array slices.
Refine the way BaseCursorResult and CursorMetaData
interact with caching; to suit cases like Alembic
modifying table structures, don't cache the
cursor metadata if it were created against a
cursor.description using non-positional matching,
e.g. "select *". if a table re-ordered its columns
or added/removed, now that data is obsolete.
Additionally we have to adapt the cursor metadata
_keymap regardless of if we just processed
cursor.description, because if we ran against
a cached SQLCompiler we won't have the right
columns in _keymap.
Other refinements to how and when we do this
adaption as some weird cases
were exposed in the Postgresql dialect,
a text() construct that names just one column that
is not actually in the statement. Fixed that
also as it looks like a cut-and-paste artifact
that doesn't actually affect anything.
Various issues with re-use of compiled result maps
and cursor metadata in conjunction with tables being
changed, such as change in order of columns.
mappers can be cleared but the class remains, meaning
a mapper has to use itself as the cache key not the class.
lots of bound parameter / literal issues, due to Alembic
creating a straight subclass of bindparam that renders
inline directly. While we can update Alembic to not
do this, we have to assume other people might be doing
this, so bindparam() implements the inherit_cache=True
logic as well that was a bit involved.
turn on cache stats in logging.
Includes a fix to subqueryloader which moves all setup to
the create_row_processor() phase and elminates any storage
within the compiled context. This includes some changes
to create_row_processor() signature and a revising of the
technique used to determine if the loader can participate
in polymorphic queries, which is also applied to
selectinloading.
DML update.values() and ordered_values() now coerces the
keys as we have tests that pass an arbitrary class here
which only includes __clause_element__(), so the
key can't be cached unless it is coerced. this in turn
changed how composite attributes support bulk update
to use the standard approach of ClauseElement with
annotations that are parsed in the ORM context.
memory profiling successfully caught that the Session
from Query was getting passed into _statement_20()
so that was a big win for that test suite.
Apparently Compiler had .execute() and .scalar() methods
stuck on it, these date back to version 0.4 and there
was a single test in the PostgreSQL dialect tests
that exercised it for no apparent reason. Removed
these methods as well as the concept of a Compiler
holding onto a "bind".
Fixes: #5386
Change-Id: I990b43aab96b42665af1b2187ad6020bee778784
Diffstat (limited to 'doc/build/orm/tutorial.rst')
-rw-r--r-- | doc/build/orm/tutorial.rst | 156 |
1 files changed, 78 insertions, 78 deletions
diff --git a/doc/build/orm/tutorial.rst b/doc/build/orm/tutorial.rst index c08daa7fd..8ea8af4a2 100644 --- a/doc/build/orm/tutorial.rst +++ b/doc/build/orm/tutorial.rst @@ -210,16 +210,16 @@ the actual ``CREATE TABLE`` statement: >>> Base.metadata.create_all(engine) PRAGMA main.table_info("users") - () + [...] () PRAGMA temp.table_info("users") - () + [...] () CREATE TABLE users ( id INTEGER NOT NULL, name VARCHAR, fullname VARCHAR, nickname VARCHAR, PRIMARY KEY (id) ) - () + [...] () COMMIT .. topic:: Minimal Table Descriptions vs. Full Descriptions @@ -373,7 +373,7 @@ added: {sql}>>> our_user = session.query(User).filter_by(name='ed').first() # doctest:+NORMALIZE_WHITESPACE BEGIN (implicit) INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) - ('ed', 'Ed Jones', 'edsnickname') + [...] ('ed', 'Ed Jones', 'edsnickname') SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, @@ -381,7 +381,7 @@ added: FROM users WHERE users.name = ? LIMIT ? OFFSET ? - ('ed', 1, 0) + [...] ('ed', 1, 0) {stop}>>> our_user <User(name='ed', fullname='Ed Jones', nickname='edsnickname')> @@ -448,13 +448,13 @@ three new ``User`` objects we've added: {sql}>>> session.commit() UPDATE users SET nickname=? WHERE users.id = ? - ('eddie', 1) + [...] ('eddie', 1) INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) - ('wendy', 'Wendy Williams', 'windy') + [...] ('wendy', 'Wendy Williams', 'windy') INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) - ('mary', 'Mary Contrary', 'mary') + [...] ('mary', 'Mary Contrary', 'mary') INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) - ('fred', 'Fred Flintstone', 'freddy') + [...] ('fred', 'Fred Flintstone', 'freddy') COMMIT :meth:`~.Session.commit` flushes the remaining changes to the @@ -475,7 +475,7 @@ If we look at Ed's ``id`` attribute, which earlier was ``None``, it now has a va users.nickname AS users_nickname FROM users WHERE users.id = ? - (1,) + [...] (1,) {stop}1 After the :class:`~sqlalchemy.orm.session.Session` inserts new rows in the @@ -519,16 +519,16 @@ Querying the session, we can see that they're flushed into the current transacti {sql}>>> session.query(User).filter(User.name.in_(['Edwardo', 'fakeuser'])).all() UPDATE users SET name=? WHERE users.id = ? - ('Edwardo', 1) + [...] ('Edwardo', 1) INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) - ('fakeuser', 'Invalid', '12345') + [...] ('fakeuser', 'Invalid', '12345') SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.nickname AS users_nickname FROM users WHERE users.name IN (?, ?) - ('Edwardo', 'fakeuser') + [...] ('Edwardo', 'fakeuser') {stop}[<User(name='Edwardo', fullname='Ed Jones', nickname='eddie')>, <User(name='fakeuser', fullname='Invalid', nickname='12345')>] Rolling back, we can see that ``ed_user``'s name is back to ``ed``, and @@ -548,7 +548,7 @@ Rolling back, we can see that ``ed_user``'s name is back to ``ed``, and users.nickname AS users_nickname FROM users WHERE users.id = ? - (1,) + [...] (1,) {stop}u'ed' >>> fake_user in session False @@ -564,7 +564,7 @@ issuing a SELECT illustrates the changes made to the database: users.nickname AS users_nickname FROM users WHERE users.name IN (?, ?) - ('ed', 'fakeuser') + [...] ('ed', 'fakeuser') {stop}[<User(name='ed', fullname='Ed Jones', nickname='eddie')>] .. _ormtutorial_querying: @@ -590,7 +590,7 @@ returned: users.fullname AS users_fullname, users.nickname AS users_nickname FROM users ORDER BY users.id - () + [...] () {stop}ed Ed Jones wendy Wendy Williams mary Mary Contrary @@ -609,7 +609,7 @@ is expressed as tuples: SELECT users.name AS users_name, users.fullname AS users_fullname FROM users - () + [...] () {stop}ed Ed Jones wendy Wendy Williams mary Mary Contrary @@ -630,7 +630,7 @@ class: users.fullname AS users_fullname, users.nickname AS users_nickname FROM users - () + [...] () {stop}<User(name='ed', fullname='Ed Jones', nickname='eddie')> ed <User(name='wendy', fullname='Wendy Williams', nickname='windy')> wendy <User(name='mary', fullname='Mary Contrary', nickname='mary')> mary @@ -647,7 +647,7 @@ is mapped to one (such as ``User.name``): ... print(row.name_label) SELECT users.name AS name_label FROM users - (){stop} + [...] (){stop} ed wendy mary @@ -669,7 +669,7 @@ entities are present in the call to :meth:`~.Session.query`, can be controlled u user_alias.fullname AS user_alias_fullname, user_alias.nickname AS user_alias_nickname FROM users AS user_alias - (){stop} + [...] (){stop} <User(name='ed', fullname='Ed Jones', nickname='eddie')> <User(name='wendy', fullname='Wendy Williams', nickname='windy')> <User(name='mary', fullname='Mary Contrary', nickname='mary')> @@ -689,7 +689,7 @@ conjunction with ORDER BY: users.nickname AS users_nickname FROM users ORDER BY users.id LIMIT ? OFFSET ? - (2, 1){stop} + [...] (2, 1){stop} <User(name='wendy', fullname='Wendy Williams', nickname='windy')> <User(name='mary', fullname='Mary Contrary', nickname='mary')> @@ -703,7 +703,7 @@ and filtering results, which is accomplished either with ... print(name) SELECT users.name AS users_name FROM users WHERE users.fullname = ? - ('Ed Jones',) + [...] ('Ed Jones',) {stop}ed ...or :func:`~sqlalchemy.orm.query.Query.filter`, which uses more flexible SQL @@ -717,7 +717,7 @@ operators with the class-level attributes on your mapped class: ... print(name) SELECT users.name AS users_name FROM users WHERE users.fullname = ? - ('Ed Jones',) + [...] ('Ed Jones',) {stop}ed The :class:`~sqlalchemy.orm.query.Query` object is fully **generative**, meaning @@ -739,7 +739,7 @@ users named "ed" with a full name of "Ed Jones", you can call users.nickname AS users_nickname FROM users WHERE users.name = ? AND users.fullname = ? - ('ed', 'Ed Jones') + [...] ('ed', 'Ed Jones') {stop}<User(name='ed', fullname='Ed Jones', nickname='eddie')> Common Filter Operators @@ -861,7 +861,7 @@ database results. Here's a brief tour: users.nickname AS users_nickname FROM users WHERE users.name LIKE ? ORDER BY users.id - ('%ed',) + [...] ('%ed',) {stop}[<User(name='ed', fullname='Ed Jones', nickname='eddie')>, <User(name='fred', fullname='Fred Flintstone', nickname='freddy')>] @@ -893,7 +893,7 @@ database results. Here's a brief tour: FROM users WHERE users.name LIKE ? ORDER BY users.id LIMIT ? OFFSET ? - ('%ed', 1, 0) + [...] ('%ed', 1, 0) {stop}<User(name='ed', fullname='Ed Jones', nickname='eddie')> * :meth:`_query.Query.one()` fully fetches all rows, and if not @@ -937,7 +937,7 @@ database results. Here's a brief tour: SELECT users.id AS users_id FROM users WHERE users.name = ? ORDER BY users.id - ('ed',) + [...] ('ed',) {stop}1 .. _orm_tutorial_literal_sql: @@ -965,7 +965,7 @@ by most applicable methods. For example, users.nickname AS users_nickname FROM users WHERE id<224 ORDER BY id - () + [...] () {stop}ed wendy mary @@ -985,7 +985,7 @@ method: users.nickname AS users_nickname FROM users WHERE id<? and name=? ORDER BY users.id - (224, 'fred') + [...] (224, 'fred') {stop}<User(name='fred', fullname='Fred Flintstone', nickname='freddy')> To use an entirely string-based statement, a :func:`_expression.text` construct @@ -999,7 +999,7 @@ returned by the SQL statement based on column name:: {sql}>>> session.query(User).from_statement( ... text("SELECT * FROM users where name=:name")).params(name='ed').all() SELECT * FROM users where name=? - ('ed',) + [...] ('ed',) {stop}[<User(name='ed', fullname='Ed Jones', nickname='eddie')>] For better targeting of mapped columns to a textual SELECT, as well as to @@ -1013,7 +1013,7 @@ columns are passed in the desired order to :meth:`_expression.TextClause.columns >>> stmt = stmt.columns(User.name, User.id, User.fullname, User.nickname) {sql}>>> session.query(User).from_statement(stmt).params(name='ed').all() SELECT name, id, fullname, nickname FROM users where name=? - ('ed',) + [...] ('ed',) {stop}[<User(name='ed', fullname='Ed Jones', nickname='eddie')>] When selecting from a :func:`_expression.text` construct, the :class:`_query.Query` @@ -1028,7 +1028,7 @@ any other case: {sql}>>> session.query(User.id, User.name).\ ... from_statement(stmt).params(name='ed').all() SELECT name, id FROM users where name=? - ('ed',) + [...] ('ed',) {stop}[(1, u'ed')] .. seealso:: @@ -1052,7 +1052,7 @@ counting called :meth:`~sqlalchemy.orm.query.Query.count()`: users.nickname AS users_nickname FROM users WHERE users.name LIKE ?) AS anon_1 - ('%ed',) + [...] ('%ed',) {stop}2 .. sidebar:: Counting on ``count()`` @@ -1085,7 +1085,7 @@ use it to return the count of each distinct user name: {sql}>>> session.query(func.count(User.name), User.name).group_by(User.name).all() SELECT count(users.name) AS count_1, users.name AS users_name FROM users GROUP BY users.name - () + [...] () {stop}[(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')] To achieve our simple ``SELECT count(*) FROM table``, we can apply it as: @@ -1095,7 +1095,7 @@ To achieve our simple ``SELECT count(*) FROM table``, we can apply it as: {sql}>>> session.query(func.count('*')).select_from(User).scalar() SELECT count(?) AS count_1 FROM users - ('*',) + [...] ('*',) {stop}4 The usage of :meth:`_query.Query.select_from` can be removed if we express the count in terms @@ -1106,7 +1106,7 @@ of the ``User`` primary key directly: {sql}>>> session.query(func.count(User.id)).scalar() SELECT count(users.id) AS count_1 FROM users - () + [...] () {stop}4 .. _orm_tutorial_relationship: @@ -1223,7 +1223,7 @@ already been created: PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES users (id) ) - () + [...] () COMMIT Working with Related Objects @@ -1272,11 +1272,11 @@ known as **cascading**: >>> session.add(jack) {sql}>>> session.commit() INSERT INTO users (name, fullname, nickname) VALUES (?, ?, ?) - ('jack', 'Jack Bean', 'gjffdd') + [...] ('jack', 'Jack Bean', 'gjffdd') INSERT INTO addresses (email_address, user_id) VALUES (?, ?) - ('jack@google.com', 5) + [...] ('jack@google.com', 5) INSERT INTO addresses (email_address, user_id) VALUES (?, ?) - ('j25@yahoo.com', 5) + [...] ('j25@yahoo.com', 5) COMMIT Querying for Jack, we get just Jack back. No SQL is yet issued for Jack's addresses: @@ -1292,7 +1292,7 @@ Querying for Jack, we get just Jack back. No SQL is yet issued for Jack's addre users.nickname AS users_nickname FROM users WHERE users.name = ? - ('jack',) + [...] ('jack',) {stop}>>> jack <User(name='jack', fullname='Jack Bean', nickname='gjffdd')> @@ -1308,7 +1308,7 @@ Let's look at the ``addresses`` collection. Watch the SQL: addresses.user_id AS addresses_user_id FROM addresses WHERE ? = addresses.user_id ORDER BY addresses.id - (5,) + [...] (5,) {stop}[<Address(email_address='jack@google.com')>, <Address(email_address='j25@yahoo.com')>] When we accessed the ``addresses`` collection, SQL was suddenly issued. This @@ -1349,7 +1349,7 @@ Below we load the ``User`` and ``Address`` entities at once using this method: FROM users, addresses WHERE users.id = addresses.user_id AND addresses.email_address = ? - ('jack@google.com',) + [...] ('jack@google.com',) {stop}<User(name='jack', fullname='Jack Bean', nickname='gjffdd')> <Address(email_address='jack@google.com')> @@ -1367,7 +1367,7 @@ using the :meth:`_query.Query.join` method: users.nickname AS users_nickname FROM users JOIN addresses ON users.id = addresses.user_id WHERE addresses.email_address = ? - ('jack@google.com',) + [...] ('jack@google.com',) {stop}[<User(name='jack', fullname='Jack Bean', nickname='gjffdd')>] :meth:`_query.Query.join` knows how to join between ``User`` @@ -1435,7 +1435,7 @@ distinct email addresses at the same time: ON users.id = addresses_2.user_id WHERE addresses_1.email_address = ? AND addresses_2.email_address = ? - ('jack@google.com', 'j25@yahoo.com') + [...] ('jack@google.com', 'j25@yahoo.com') {stop}jack jack@google.com j25@yahoo.com In addition to using the :meth:`_orm.PropComparator.of_type` method, it is @@ -1496,7 +1496,7 @@ accessible through an attribute called ``c``: FROM addresses GROUP BY addresses.user_id) AS anon_1 ON users.id = anon_1.user_id ORDER BY users.id - ('*',) + [...] ('*',) {stop}<User(name='ed', fullname='Ed Jones', nickname='eddie')> None <User(name='wendy', fullname='Wendy Williams', nickname='windy')> None <User(name='mary', fullname='Mary Contrary', nickname='mary')> None @@ -1534,7 +1534,7 @@ to associate an "alias" of a mapped class to a subquery: FROM addresses WHERE addresses.email_address != ?) AS anon_1 ON users.id = anon_1.user_id - ('j25@yahoo.com',) + [...] ('j25@yahoo.com',) {stop}<User(name='jack', fullname='Jack Bean', nickname='gjffdd')> <Address(email_address='jack@google.com')> @@ -1559,7 +1559,7 @@ There is an explicit EXISTS construct, which looks like this: WHERE EXISTS (SELECT * FROM addresses WHERE addresses.user_id = users.id) - () + [...] () {stop}jack The :class:`~sqlalchemy.orm.query.Query` features several operators which make @@ -1576,7 +1576,7 @@ usage of EXISTS automatically. Above, the statement can be expressed along the WHERE EXISTS (SELECT 1 FROM addresses WHERE users.id = addresses.user_id) - () + [...] () {stop}jack :meth:`~.RelationshipProperty.Comparator.any` takes criterion as well, to limit the rows matched: @@ -1591,7 +1591,7 @@ usage of EXISTS automatically. Above, the statement can be expressed along the WHERE EXISTS (SELECT 1 FROM addresses WHERE users.id = addresses.user_id AND addresses.email_address LIKE ?) - ('%google%',) + [...] ('%google%',) {stop}jack :meth:`~.RelationshipProperty.Comparator.has` is the same operator as @@ -1609,7 +1609,7 @@ usage of EXISTS automatically. Above, the statement can be expressed along the WHERE NOT (EXISTS (SELECT 1 FROM users WHERE users.id = addresses.user_id AND users.name = ?)) - ('jack',) + [...] ('jack',) {stop}[] Common Relationship Operators @@ -1685,14 +1685,14 @@ at once: users.nickname AS users_nickname FROM users WHERE users.name = ? - ('jack',) + [...] ('jack',) SELECT addresses.user_id AS addresses_user_id, addresses.id AS addresses_id, addresses.email_address AS addresses_email_address FROM addresses WHERE addresses.user_id IN (?) ORDER BY addresses.id - (5,) + [...] (5,) {stop}>>> jack <User(name='jack', fullname='Jack Bean', nickname='gjffdd')> @@ -1728,7 +1728,7 @@ will emit the extra join regardless: FROM users LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id WHERE users.name = ? ORDER BY addresses_1.id - ('jack',) + [...] ('jack',) {stop}>>> jack <User(name='jack', fullname='Jack Bean', nickname='gjffdd')> @@ -1792,7 +1792,7 @@ attribute: addresses.user_id AS addresses_user_id FROM addresses JOIN users ON users.id = addresses.user_id WHERE users.name = ? - ('jack',) + [...] ('jack',) {stop}>>> jacks_addresses [<Address(email_address='jack@google.com')>, <Address(email_address='j25@yahoo.com')>] @@ -1814,9 +1814,9 @@ in the session, then we'll issue a ``count`` query to see that no rows remain: >>> session.delete(jack) {sql}>>> session.query(User).filter_by(name='jack').count() UPDATE addresses SET user_id=? WHERE addresses.id = ? - ((None, 1), (None, 2)) + [...] ((None, 1), (None, 2)) DELETE FROM users WHERE users.id = ? - (5,) + [...] (5,) SELECT count(*) AS count_1 FROM (SELECT users.id AS users_id, users.name AS users_name, @@ -1824,7 +1824,7 @@ in the session, then we'll issue a ``count`` query to see that no rows remain: users.nickname AS users_nickname FROM users WHERE users.name = ?) AS anon_1 - ('jack',) + [...] ('jack',) {stop}0 So far, so good. How about Jack's ``Address`` objects ? @@ -1840,7 +1840,7 @@ So far, so good. How about Jack's ``Address`` objects ? addresses.user_id AS addresses_user_id FROM addresses WHERE addresses.email_address IN (?, ?)) AS anon_1 - ('jack@google.com', 'j25@yahoo.com') + [...] ('jack@google.com', 'j25@yahoo.com') {stop}2 Uh oh, they're still there ! Analyzing the flush SQL, we can see that the @@ -1914,7 +1914,7 @@ being deleted: users.nickname AS users_nickname FROM users WHERE users.id = ? - (5,) + [...] (5,) {stop} # remove one Address (lazy load fires off) @@ -1924,7 +1924,7 @@ being deleted: addresses.user_id AS addresses_user_id FROM addresses WHERE ? = addresses.user_id - (5,) + [...] (5,) {stop} # only one address remains @@ -1932,14 +1932,14 @@ being deleted: ... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com']) ... ).count() DELETE FROM addresses WHERE addresses.id = ? - (2,) + [...] (2,) SELECT count(*) AS count_1 FROM (SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id FROM addresses WHERE addresses.email_address IN (?, ?)) AS anon_1 - ('jack@google.com', 'j25@yahoo.com') + [...] ('jack@google.com', 'j25@yahoo.com') {stop}1 Deleting Jack will delete both Jack and the remaining ``Address`` associated @@ -1951,9 +1951,9 @@ with the user: {sql}>>> session.query(User).filter_by(name='jack').count() DELETE FROM addresses WHERE addresses.id = ? - (1,) + [...] (1,) DELETE FROM users WHERE users.id = ? - (5,) + [...] (5,) SELECT count(*) AS count_1 FROM (SELECT users.id AS users_id, users.name AS users_name, @@ -1961,7 +1961,7 @@ with the user: users.nickname AS users_nickname FROM users WHERE users.name = ?) AS anon_1 - ('jack',) + [...] ('jack',) {stop}0 {sql}>>> session.query(Address).filter( @@ -1973,7 +1973,7 @@ with the user: addresses.user_id AS addresses_user_id FROM addresses WHERE addresses.email_address IN (?, ?)) AS anon_1 - ('jack@google.com', 'j25@yahoo.com') + [...] ('jack@google.com', 'j25@yahoo.com') {stop}0 .. topic:: More on Cascades @@ -2086,7 +2086,7 @@ Create new tables: PRIMARY KEY (id), UNIQUE (keyword) ) - () + [...] () COMMIT CREATE TABLE posts ( id INTEGER NOT NULL, @@ -2096,7 +2096,7 @@ Create new tables: PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES users (id) ) - () + [...] () COMMIT CREATE TABLE post_keywords ( post_id INTEGER NOT NULL, @@ -2105,7 +2105,7 @@ Create new tables: FOREIGN KEY(post_id) REFERENCES posts (id), FOREIGN KEY(keyword_id) REFERENCES keywords (id) ) - () + [...] () COMMIT Usage is not too different from what we've been doing. Let's give Wendy some blog posts: @@ -2121,7 +2121,7 @@ Usage is not too different from what we've been doing. Let's give Wendy some bl users.nickname AS users_nickname FROM users WHERE users.name = ? - ('wendy',) + [...] ('wendy',) {stop} >>> post = BlogPost("Wendy's Blog Post", "This is a test", wendy) >>> session.add(post) @@ -2144,13 +2144,13 @@ keyword string 'firstpost'": ... filter(BlogPost.keywords.any(keyword='firstpost')).\ ... all() INSERT INTO keywords (keyword) VALUES (?) - ('wendy',) + [...] ('wendy',) INSERT INTO keywords (keyword) VALUES (?) - ('firstpost',) + [...] ('firstpost',) INSERT INTO posts (user_id, headline, body) VALUES (?, ?, ?) - (2, "Wendy's Blog Post", 'This is a test') + [...] (2, "Wendy's Blog Post", 'This is a test') INSERT INTO post_keywords (post_id, keyword_id) VALUES (?, ?) - (...) + [...] (...) SELECT posts.id AS posts_id, posts.user_id AS posts_user_id, posts.headline AS posts_headline, @@ -2161,7 +2161,7 @@ keyword string 'firstpost'": WHERE posts.id = post_keywords.post_id AND keywords.id = post_keywords.keyword_id AND keywords.keyword = ?) - ('firstpost',) + [...] ('firstpost',) {stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User(name='wendy', fullname='Wendy Williams', nickname='windy')>)] If we want to look up posts owned by the user ``wendy``, we can tell @@ -2183,7 +2183,7 @@ the query to narrow down to that ``User`` object as a parent: WHERE posts.id = post_keywords.post_id AND keywords.id = post_keywords.keyword_id AND keywords.keyword = ?)) - (2, 'firstpost') + [...] (2, 'firstpost') {stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User(name='wendy', fullname='Wendy Williams', nickname='windy')>)] Or we can use Wendy's own ``posts`` relationship, which is a "dynamic" @@ -2204,7 +2204,7 @@ relationship, to query straight from there: WHERE posts.id = post_keywords.post_id AND keywords.id = post_keywords.keyword_id AND keywords.keyword = ?)) - (2, 'firstpost') + [...] (2, 'firstpost') {stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User(name='wendy', fullname='Wendy Williams', nickname='windy')>)] Further Reference |