Skip to content

Commit 494480c

Browse files
committed
Refs #253, added relationship docs for distinct loader
1 parent e561dd7 commit 494480c

File tree

2 files changed

+129
-29
lines changed

2 files changed

+129
-29
lines changed

docs/faq.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ modify the code to explicitly use the correct connection::
5656

5757
async with db.transaction() as tx:
5858
await MyModel.create(name='xxx', bind=tx.connection)
59+
60+
.. tip::
61+
62+
Since GINO 0.7.4, ``aiocontextvars`` became a required dependency.

docs/relationship.rst

Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ And subloaders can be nested::
217217
loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
218218

219219
By now, GINO supports only loading many-to-one joined query. To modify a
220-
relationship, just modify the reference column.
220+
relationship, just modify the reference column values.
221221

222222

223223
Self Referencing
@@ -265,31 +265,127 @@ The generated SQL looks like this:
265265
Other Relationships
266266
-------------------
267267

268-
GINO does not have the ability to reduce a result set yet, so by now
269-
one-to-many, many-to-many and one-to-one relationships have to be done
270-
manually. You can do this in many different ways, the topic is out of scope.
271-
But let's try to load a one-to-many relationship of the same child-parent
272-
example through the :class:`~gino.loader.CallableLoader`::
273-
274-
async def main():
275-
parents = {}
276-
277-
parent_loader = Parent.load()
278-
child_loader = Child.load()
279-
280-
def loader(row, ctx):
281-
parent_id = row[Parent.id]
282-
parent = parents.get(parent_id, None)
283-
if parent is None:
284-
parent, distinct = parent_loader.do_load(row, ctx)
285-
parent.children = []
286-
parents[parent_id] = parent
287-
if row[Child.id] is not None:
288-
child, distinct = child_loader.do_load(row, ctx)
289-
child.parent = parent # two-way reference
290-
parent.children.append(child)
291-
292-
await Parent.outerjoin(Child).select().gino.load(loader).all()
293-
294-
for parent in parents.values():
295-
print(f'Parent: {parent.id}, children: {len(parent.children)}')
268+
GINO 0.7.4 introduced an experimental distinct feature to reduce a result set
269+
with loaders, combining rows under specified conditions. This made it possible
270+
to build one-to-many relationships. Using the same parent-child example above,
271+
we could load distinct parents with all their children::
272+
273+
class Parent(db.Model):
274+
__tablename__ = 'parents'
275+
id = db.Column(db.Integer, primary_key=True)
276+
277+
def __init__(self, **kw):
278+
super().__init__(**kw)
279+
self._children = set()
280+
281+
@property
282+
def children(self):
283+
return self._children
284+
285+
@children.setter
286+
def add_child(self, child):
287+
self._children.add(child)
288+
289+
290+
class Child(db.Model):
291+
__tablename__ = 'children'
292+
id = db.Column(db.Integer, primary_key=True)
293+
parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
294+
295+
296+
query = Child.outerjoin(Parent).select()
297+
parents = await query.gino.load(
298+
Parent.distinct(Parent.id).load(add_child=Child)).all()
299+
300+
Here the query is still child outer-joining parent, but the loader is loading
301+
parent instances with distinct IDs only, while storing all their children
302+
through the ``add_child`` setter property. In detail for each row, a parent
303+
instance is firstly loaded if no parent instance with the same ID was loaded
304+
previously, or the same parent instance will be reused. Then a child instance
305+
is loaded from the same row, and fed to the possibly reused parent instance by
306+
``parent.add_child = new_child``.
307+
308+
Distinct loaders can be nested to load hierarchical data, but it cannot be used
309+
as a query builder to automatically generate queries.
310+
311+
GINO provides no additional support for one-to-one relationship - the user
312+
should make sure that the query produces rows of distinct instance pairs, and
313+
load them with regular GINO model loaders. When in doubt, the distinct feature
314+
can be used on both sides, but you'll have to manually deal with the conflict
315+
if more than one related instances are found. For example, we could keep only
316+
the last child for each parent::
317+
318+
class Parent(db.Model):
319+
__tablename__ = 'parents'
320+
id = db.Column(db.Integer, primary_key=True)
321+
322+
def __init__(self, **kw):
323+
super().__init__(**kw)
324+
self._child = None
325+
326+
@property
327+
def child(self):
328+
return self._child
329+
330+
@child.setter
331+
def child(self, child):
332+
self._child = child
333+
334+
335+
class Child(db.Model):
336+
__tablename__ = 'children'
337+
id = db.Column(db.Integer, primary_key=True)
338+
parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
339+
340+
341+
query = Child.outerjoin(Parent).select()
342+
parents = await query.gino.load(
343+
Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
344+
345+
346+
Similarly, you can build many-to-many relationships in the same way::
347+
348+
class Parent(db.Model):
349+
__tablename__ = 'parents'
350+
id = db.Column(db.Integer, primary_key=True)
351+
352+
def __init__(self, **kw):
353+
super().__init__(**kw)
354+
self._children = set()
355+
356+
@property
357+
def children(self):
358+
return self._children
359+
360+
@children.setter
361+
def add_child(self, child):
362+
self._children.add(child)
363+
child._parents.add(self)
364+
365+
366+
class Child(db.Model):
367+
__tablename__ = 'children'
368+
id = db.Column(db.Integer, primary_key=True)
369+
370+
def __init__(self, **kw):
371+
super().__init__(**kw)
372+
self._parents = set()
373+
374+
@property
375+
def parents(self):
376+
return self._parents
377+
378+
379+
class ParentXChild(db.Model):
380+
__tablename__ = 'parents_x_children'
381+
382+
parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
383+
child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
384+
385+
386+
query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
387+
parents = await query.gino.load(
388+
Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
389+
390+
Likewise, there is for now no way to modify the relationships automatically,
391+
you'll have to manually create, delete or modify ``ParentXChild`` instances.

0 commit comments

Comments
 (0)