@@ -217,7 +217,7 @@ And subloaders can be nested::
217
217
loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
218
218
219
219
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 .
221
221
222
222
223
223
Self Referencing
@@ -265,31 +265,127 @@ The generated SQL looks like this:
265
265
Other Relationships
266
266
-------------------
267
267
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