Skip to content

Commit c67d44a

Browse files
committed
Add helper classes OrderItem and ConditionItem to represent the items
in the ORDER BY and the WHERE clause respectively
1 parent ca98b69 commit c67d44a

1 file changed

Lines changed: 95 additions & 55 deletions

File tree

src/icat/query.py

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,78 @@
5959
:meth:`icat.query.Query.setJoinSpecs` method.
6060
"""
6161

62+
# ========================= helper classes ===========================
63+
# Note: these helpers are needed in the internal data structures in
64+
# class Query. There are intentionally not included in the
65+
# python-icat documentation and are not considered part of the API.
66+
# There is no commitement on compatibility between versions.
67+
68+
class ItemBase:
69+
"""Abstract base class for OrderItem and ConditionItem.
70+
"""
71+
72+
db_func_re = re.compile(r"(?:([A-Za-z_]+)\()?([A-Za-z.]+)(?(1)\))")
73+
74+
def split_db_functs(self, attr):
75+
m = self.db_func_re.fullmatch(attr)
76+
if not m:
77+
raise ValueError("Invalid attribute '%s'" % attr)
78+
return m.group(2,1)
79+
80+
81+
class OrderItem(ItemBase):
82+
"""Represent an item in the ORDER BY clause.
83+
"""
84+
85+
def __init__(self, obj, cloneFrom=None):
86+
if cloneFrom:
87+
self.attr = obj or cloneFrom.attr
88+
self.jpql_func = cloneFrom.jpql_func
89+
self.direction = cloneFrom.direction
90+
else:
91+
if isinstance(obj, tuple):
92+
obj, direction = obj
93+
if direction not in ("ASC", "DESC"):
94+
raise ValueError("Invalid ordering direction '%s'"
95+
% direction)
96+
else:
97+
direction = None
98+
attr, jpql_func = self.split_db_functs(obj)
99+
self.attr = attr
100+
self.jpql_func = jpql_func
101+
self.direction = direction
102+
103+
@property
104+
def formatstr(self):
105+
if self.jpql_func:
106+
if self.direction:
107+
return "%s(%%s) %s" % (self.jpql_func, self.direction)
108+
else:
109+
return "%s(%%s)" % self.jpql_func
110+
else:
111+
if self.direction:
112+
return "%%s %s" % self.direction
113+
else:
114+
return "%s"
115+
116+
117+
class ConditionItem(ItemBase):
118+
"""Represent an item in the WHERE clause.
119+
"""
120+
def __init__(self, obj, rhs):
121+
attr, jpql_func = self.split_db_functs(obj)
122+
self.attr = attr
123+
self.jpql_func = jpql_func
124+
self.rhs = rhs
125+
126+
@property
127+
def formatstr(self):
128+
rhs = self.rhs.replace('%', '%%')
129+
if self.jpql_func:
130+
return "%s(%%s) %s" % (self.jpql_func, rhs)
131+
else:
132+
return "%%s %s" % (rhs)
133+
62134
# ========================== class Query =============================
63135

64136
class Query():
@@ -106,8 +178,6 @@ class Query():
106178
add the `join_specs` argument.
107179
"""
108180

109-
_db_func_re = re.compile(r"(?:([A-Za-z_]+)\()?([A-Za-z.]+)(?(1)\))")
110-
111181
def __init__(self, client, entity,
112182
attributes=None, aggregate=None, order=None,
113183
conditions=None, includes=None, limit=None,
@@ -205,12 +275,6 @@ def _dosubst(self, obj, subst, addas=True):
205275
n += " AS %s" % (subst[obj])
206276
return n
207277

208-
def _split_db_functs(self, attr):
209-
m = self._db_func_re.fullmatch(attr)
210-
if not m:
211-
raise ValueError("Invalid attribute '%s'" % attr)
212-
return m.group(2,1)
213-
214278
def _get_subst(self):
215279
if self._subst is None:
216280
joinattrs = ( set(self.order.keys()) |
@@ -346,22 +410,16 @@ def setOrder(self, order):
346410
if order is True:
347411

348412
for a in self.entity.getNaturalOrder(self.client):
349-
self.order[a] = "%s"
413+
item = OrderItem(a)
414+
self.order[item.attr] = item
350415

351416
elif order:
352417

353418
for obj in order:
354419

355-
if isinstance(obj, tuple):
356-
obj, direction = obj
357-
if direction not in ("ASC", "DESC"):
358-
raise ValueError("Invalid ordering direction '%s'"
359-
% direction)
360-
else:
361-
direction = None
362-
attr, jpql_func = self._split_db_functs(obj)
420+
item = OrderItem(obj)
363421

364-
for (pattr, attrInfo, rclass) in self._attrpath(attr):
422+
for (pattr, attrInfo, rclass) in self._attrpath(item.attr):
365423
if attrInfo.relType == "ONE":
366424
if (not attrInfo.notNullable and
367425
pattr not in self.conditions and
@@ -375,33 +433,24 @@ def setOrder(self, order):
375433
warn(QueryOneToManyOrderWarning(pattr),
376434
stacklevel=sl)
377435

378-
if jpql_func:
379-
if rclass is not None:
380-
raise ValueError("Cannot apply a JPQL function "
381-
"to a related object: %s" % obj)
382-
if direction:
383-
vstr = "%s(%%s) %s" % (jpql_func, direction)
384-
else:
385-
vstr = "%s(%%s)" % jpql_func
386-
else:
387-
if direction:
388-
vstr = "%%s %s" % direction
389-
else:
390-
vstr = "%s"
391436
if rclass is None:
392-
# attr is an attribute, use it right away.
393-
if attr in self.order:
394-
raise ValueError("Cannot add %s more than once" % attr)
395-
self.order[attr] = vstr
437+
# the item is an attribute, use it right away.
438+
if item.attr in self.order:
439+
raise ValueError("Cannot add %s more than once" % item.attr)
440+
self.order[item.attr] = item
396441
else:
397442
# attr is a related object, use the natural order
398443
# of its class.
444+
if item.jpql_func:
445+
raise ValueError("Cannot apply a JPQL function "
446+
"to a related object: %s" % obj)
399447
for ra in rclass.getNaturalOrder(self.client):
400-
rattr = "%s.%s" % (attr, ra)
448+
rattr = "%s.%s" % (item.attr, ra)
401449
if rattr in self.order:
402450
raise ValueError("Cannot add %s more than once"
403451
% rattr)
404-
self.order[rattr] = vstr
452+
ritem = OrderItem(rattr, cloneFrom=item)
453+
self.order[ritem.attr] = ritem
405454

406455
def addConditions(self, conditions):
407456
"""Add conditions to the constraints to build the WHERE clause from.
@@ -422,27 +471,19 @@ def addConditions(self, conditions):
422471
.. versionchanged:: 0.20.0
423472
allow a JPQL function in the attribute.
424473
"""
425-
def _cond_value(rhs, func):
426-
rhs = rhs.replace('%', '%%')
427-
if func:
428-
return "%s(%%s) %s" % (func, rhs)
429-
else:
430-
return "%%s %s" % (rhs)
431474
if conditions:
432475
self._subst = None
433476
for k in conditions.keys():
434477
if isinstance(conditions[k], str):
435478
conds = [conditions[k]]
436479
else:
437480
conds = conditions[k]
438-
a, jpql_func = self._split_db_functs(k)
439-
for (pattr, attrInfo, rclass) in self._attrpath(a):
440-
pass
441-
v = [ _cond_value(rhs, jpql_func) for rhs in conds ]
442-
if a in self.conditions:
443-
self.conditions[a].extend(v)
444-
else:
445-
self.conditions[a] = v
481+
for rhs in conds:
482+
item = ConditionItem(k, rhs)
483+
for (pattr, attrInfo, rclass) in self._attrpath(item.attr):
484+
pass
485+
l = self.conditions.setdefault(item.attr, [])
486+
l.append(item)
446487

447488
def addIncludes(self, includes):
448489
"""Add related objects to build the INCLUDE clause from.
@@ -534,7 +575,7 @@ def where_clause(self):
534575
for a in sorted(self.conditions.keys()):
535576
attr = self._dosubst(a, subst, False)
536577
for c in self.conditions[a]:
537-
conds.append(c % attr)
578+
conds.append(c.formatstr % attr)
538579
return "WHERE " + " AND ".join(conds)
539580
else:
540581
return None
@@ -547,9 +588,8 @@ def order_clause(self):
547588
"""
548589
if self.order:
549590
subst = self._get_subst()
550-
orders = []
551-
for a in self.order.keys():
552-
orders.append(self.order[a] % self._dosubst(a, subst, False))
591+
orders = [ self.order[a].formatstr % self._dosubst(a, subst, False)
592+
for a in self.order.keys() ]
553593
return "ORDER BY " + ", ".join(orders)
554594
else:
555595
return None

0 commit comments

Comments
 (0)