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
64136class 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