4
4
from promise import dataloader , promise
5
5
from sqlalchemy .ext .hybrid import hybrid_property
6
6
from sqlalchemy .orm import (ColumnProperty , CompositeProperty ,
7
- RelationshipProperty , Session )
7
+ RelationshipProperty , Session , strategies )
8
8
from sqlalchemy .orm .exc import NoResultFound
9
9
from sqlalchemy .orm .query import QueryContext
10
- from sqlalchemy .orm .strategies import SelectInLoader
11
10
12
11
from graphene import Field
13
12
from graphene .relay import Connection , Node
@@ -213,6 +212,8 @@ def _get_custom_resolver(obj_type, orm_field_name):
213
212
def _get_relationship_resolver (obj_type , relationship_prop , model_attr ):
214
213
"""
215
214
Batch SQL queries using Dataloader to avoid the N+1 problem.
215
+ SQL batching only works for SQLAlchemy 1.2+ since it depends on
216
+ the `selectin` loader.
216
217
217
218
:param SQLAlchemyObjectType obj_type:
218
219
:param sqlalchemy.orm.properties.RelationshipProperty relationship_prop:
@@ -222,7 +223,7 @@ def _get_relationship_resolver(obj_type, relationship_prop, model_attr):
222
223
child_mapper = relationship_prop .mapper
223
224
parent_mapper = relationship_prop .parent
224
225
225
- if relationship_prop .uselist :
226
+ if not getattr ( strategies , 'SelectInLoader' , None ) or relationship_prop .uselist :
226
227
# TODO Batch many-to-many and one-to-many relationships
227
228
return _get_attr_resolver (obj_type , model_attr , model_attr )
228
229
@@ -239,14 +240,18 @@ def batch_load_fn(self, parents): # pylint: disable=method-hidden
239
240
than re-implementing and maintainnig a big chunk of the `selectin`
240
241
loader logic ourselves.
241
242
242
- The approach is to here to build a regular query that
243
+ The approach here is to build a regular query that
243
244
selects the parent and `selectin` load the relationship.
244
245
But instead of having the query emits 2 `SELECT` statements
245
246
when callling `all()`, we skip the first `SELECT` statement
246
247
and jump right before the `selectin` loader is called.
247
248
To accomplish this, we have to construct objects that are
248
249
normally built in the first part of the query in order
249
250
to call directly `SelectInLoader._load_for_path`.
251
+
252
+ TODO Move this logic to a util in the SQLAlchemy repo as per
253
+ SQLAlchemy's main maitainer suggestion.
254
+ See https://git.io/JewQ7
250
255
"""
251
256
session = Session .object_session (parents [0 ])
252
257
@@ -258,7 +263,7 @@ def batch_load_fn(self, parents): # pylint: disable=method-hidden
258
263
# The behavior of `selectin` is undefined if the parent is dirty
259
264
assert parent not in session .dirty
260
265
261
- loader = SelectInLoader (relationship_prop , (('lazy' , 'selectin' ),))
266
+ loader = strategies . SelectInLoader (relationship_prop , (('lazy' , 'selectin' ),))
262
267
263
268
# Should the boolean be set to False? Does it matter for our purposes?
264
269
states = [(sqlalchemy .inspect (parent ), True ) for parent in parents ]
0 commit comments