@@ -217,7 +217,7 @@ And subloaders can be nested::
217217 loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
218218
219219By 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
223223Self 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