@@ -129,11 +129,11 @@ def __init__(self, client, entity,
129129 if isinstance (entity , basestring ):
130130 self .entity = self .client .getEntityClass (entity )
131131 elif issubclass (entity , icat .entity .Entity ):
132- if (entity in self .client .typemap .values () and
132+ if (entity in self .client .typemap .values () and
133133 entity .BeanName is not None ):
134134 self .entity = entity
135135 else :
136- raise EntityTypeError ("Invalid entity type '%s'."
136+ raise EntityTypeError ("Invalid entity type '%s'."
137137 % entity .__name__ )
138138 else :
139139 raise EntityTypeError ("Invalid entity type '%s'." % type (entity ))
@@ -155,6 +155,7 @@ def __init__(self, client, entity,
155155 self .setOrder (order )
156156 self .setLimit (limit )
157157 self ._init = None
158+ self ._subst = None
158159
159160 def _attrpath (self , attrname ):
160161 """Follow the attribute path along related objects and iterate over
@@ -170,12 +171,12 @@ def _attrpath(self, attrname):
170171 if rclass is None :
171172 # Last component was not a relation, no further components
172173 # in the name allowed.
173- raise ValueError ("Invalid attrname '%s' for %s."
174+ raise ValueError ("Invalid attrname '%s' for %s."
174175 % (attrname , self .entity .BeanName ))
175176 attrInfo = rclass .getAttrInfo (self .client , attr )
176177 if attrInfo .relType == "ATTRIBUTE" :
177178 rclass = None
178- elif (attrInfo .relType == "ONE" or
179+ elif (attrInfo .relType == "ONE" or
179180 attrInfo .relType == "MANY" ):
180181 rclass = self .client .getEntityClass (attrInfo .type )
181182 else :
@@ -225,6 +226,14 @@ def _split_db_functs(self, attr):
225226 raise ValueError ("Invalid attribute '%s'" % attr )
226227 return m .group (2 ,1 )
227228
229+ def _get_subst (self ):
230+ if self ._subst is None :
231+ joinattrs = ( set (self .order .keys ()) |
232+ set (self .conditions .keys ()) |
233+ set (self .attributes ) )
234+ self ._subst = self ._makesubst (joinattrs )
235+ return self ._subst
236+
228237 def setAttributes (self , attributes ):
229238 """Set the attributes that the query shall return.
230239
@@ -244,6 +253,7 @@ def setAttributes(self, attributes):
244253 :meth:`setAttribute` to :meth:`setAttributes` (in the
245254 plural).
246255 """
256+ self ._subst = None
247257 self .attributes = []
248258 if attributes :
249259 if isinstance (attributes , str ):
@@ -343,6 +353,7 @@ def setOrder(self, order):
343353 .. versionchanged:: 0.20.0
344354 allow a JPQL function in the attribute.
345355 """
356+ self ._subst = None
346357 # Note: with Python 3.7 and newer we could simplify this using
347358 # a standard dict() rather then an OrderedDict().
348359 self .order = OrderedDict ()
@@ -359,7 +370,7 @@ def setOrder(self, order):
359370 if isinstance (obj , tuple ):
360371 obj , direction = obj
361372 if direction not in ("ASC" , "DESC" ):
362- raise ValueError ("Invalid ordering direction '%s'"
373+ raise ValueError ("Invalid ordering direction '%s'"
363374 % direction )
364375 else :
365376 direction = None
@@ -433,6 +444,7 @@ def _cond_value(rhs, func):
433444 else :
434445 return "%%s %s" % (rhs )
435446 if conditions :
447+ self ._subst = None
436448 for k in conditions .keys ():
437449 if isinstance (conditions [k ], basestring ):
438450 conds = [conditions [k ]]
@@ -463,7 +475,7 @@ def addIncludes(self, includes):
463475 for (pattr , attrInfo , rclass ) in self ._attrpath (iobj ):
464476 pass
465477 if rclass is None :
466- raise ValueError ("%s.%s is not a related object."
478+ raise ValueError ("%s.%s is not a related object."
467479 % (self .entity .BeanName , iobj ))
468480 self .includes .update (includes )
469481
@@ -479,36 +491,15 @@ def setLimit(self, limit):
479491 raise TypeError ("limit must be a tuple of two elements." )
480492 self .limit = limit
481493 else :
482- self .limit = None
483-
484- def __repr__ (self ):
485- """Return a formal representation of the query.
486- """
487- return ("%s(%s, %s, attributes=%s, aggregate=%s, order=%s, "
488- "conditions=%s, includes=%s, limit=%s, join_specs=%s)"
489- % (self .__class__ .__name__ ,
490- repr (self .client ), repr (self .entity .BeanName ),
491- repr (self .attributes ), repr (self .aggregate ),
492- repr (self .order ), repr (self .conditions ),
493- repr (self .includes ), repr (self .limit ),
494- repr (self .join_specs )))
494+ self .limit = None
495495
496- def __str__ (self ):
497- """Return a string representation of the query.
496+ @property
497+ def select_clause (self ):
498+ """The SELECT clause of the query.
498499
499- Note for Python 2: the result will be an unicode object if any
500- of the conditions in the query contains unicode. This
501- violates the specification of the string representation
502- operator that requires the return value to be a string object.
503- But it is the *right thing* to do to get queries with
504- non-ascii characters working. So this operator favours
505- usefulness over formal correctness. For Python 3, there is no
506- distinction between Unicode and string objects anyway.
500+ .. versionadded:: 0.21.0
507501 """
508- joinattrs = ( set (self .order .keys ()) |
509- set (self .conditions .keys ()) |
510- set (self .attributes ) )
511- subst = self ._makesubst (joinattrs )
502+ subst = self ._get_subst ()
512503 if self .attributes :
513504 attrs = []
514505 for a in self .attributes :
@@ -528,40 +519,115 @@ def __str__(self):
528519 else :
529520 for fct in reversed (self .aggregate .split (':' )):
530521 res = "%s(%s)" % (fct , res )
531- base = "SELECT %s FROM %s o" % (res , self .entity .BeanName )
532- joins = ""
522+ return "SELECT %s FROM %s o" % (res , self .entity .BeanName )
523+
524+ @property
525+ def join_clause (self ):
526+ """The JOIN clause of the query.
527+
528+ .. versionadded:: 0.21.0
529+ """
530+ subst = self ._get_subst ()
531+ joins = []
533532 for obj in sorted (subst .keys ()):
534533 js = self .join_specs .get (obj , "JOIN" )
535- joins += " %s %s" % (js , self ._dosubst (obj , subst ))
534+ joins .append ("%s %s" % (js , self ._dosubst (obj , subst )))
535+ if joins :
536+ return " " .join (joins )
537+ else :
538+ return None
539+
540+ @property
541+ def where_clause (self ):
542+ """The WHERE clause of the query.
543+
544+ .. versionadded:: 0.21.0
545+ """
546+ subst = self ._get_subst ()
536547 if self .conditions :
537548 conds = []
538549 for a in sorted (self .conditions .keys ()):
539550 attr = self ._dosubst (a , subst , False )
540551 for c in self .conditions [a ]:
541552 conds .append (c % attr )
542- where = " WHERE " + " AND " .join (conds )
553+ return " WHERE " + " AND " .join (conds )
543554 else :
544- where = ""
555+ return None
556+
557+ @property
558+ def order_clause (self ):
559+ """The ORDER BY clause of the query.
560+
561+ .. versionadded:: 0.21.0
562+ """
563+ subst = self ._get_subst ()
545564 if self .order :
546565 orders = []
547566 for a in self .order .keys ():
548567 orders .append (self .order [a ] % self ._dosubst (a , subst , False ))
549- order = " ORDER BY " + ", " .join (orders )
568+ return " ORDER BY " + ", " .join (orders )
550569 else :
551- order = ""
570+ return None
571+
572+ @property
573+ def include_clause (self ):
574+ """The INCLUDE clause of the query.
575+
576+ .. versionadded:: 0.21.0
577+ """
552578 if self .includes :
553579 subst = self ._makesubst (self .includes )
554580 includes = set (self .includes )
555581 includes .update (subst .keys ())
556582 incl = [ self ._dosubst (obj , subst ) for obj in sorted (includes ) ]
557- include = " INCLUDE " + ", " .join (incl )
583+ return " INCLUDE " + ", " .join (incl )
558584 else :
559- include = ""
585+ return None
586+
587+ @property
588+ def limit_clause (self ):
589+ """The LIMIT clause of the query.
590+
591+ .. versionadded:: 0.21.0
592+ """
560593 if self .limit :
561- limit = " LIMIT %s, %s" % self .limit
594+ return " LIMIT %s, %s" % self .limit
562595 else :
563- limit = ""
564- return base + joins + where + order + include + limit
596+ return None
597+
598+ def __repr__ (self ):
599+ """Return a formal representation of the query.
600+ """
601+ return ("%s(%s, %s, attributes=%s, aggregate=%s, order=%s, "
602+ "conditions=%s, includes=%s, limit=%s, join_specs=%s)"
603+ % (self .__class__ .__name__ ,
604+ repr (self .client ), repr (self .entity .BeanName ),
605+ repr (self .attributes ), repr (self .aggregate ),
606+ repr (self .order ), repr (self .conditions ),
607+ repr (self .includes ), repr (self .limit ),
608+ repr (self .join_specs )))
609+
610+ def __str__ (self ):
611+ """Return a string representation of the query.
612+
613+ Note for Python 2: the result will be an unicode object if any
614+ of the conditions in the query contains unicode. This
615+ violates the specification of the string representation
616+ operator that requires the return value to be a string object.
617+ But it is the *right thing* to do to get queries with
618+ non-ascii characters working. So this operator favours
619+ usefulness over formal correctness. For Python 3, there is no
620+ distinction between Unicode and string objects anyway.
621+ """
622+ clauses = filter (None , (
623+ self .select_clause ,
624+ self .join_clause ,
625+ self .where_clause ,
626+ self .order_clause ,
627+ self .include_clause ,
628+ self .limit_clause ,
629+ ))
630+ return " " .join (clauses )
565631
566632 def copy (self ):
567633 """Return an independent clone of this query.
0 commit comments