@@ -197,10 +197,22 @@ def _encode(condition):
197197##
198198## print("RallyQueryFormatter parts: %s" % repr(parts))
199199##
200-
201200 # if no CONJUNCTION is in parts, use the condition as is (simple case)
202201 conjunctions = [p for p in parts if p in RallyQueryFormatter .CONJUNCTIONS ]
203202 if not conjunctions :
203+ if mo := re .search (r'^(\w+)\s+(!?in)\s+(.+)$' , criteria , flags = re .I ):
204+ attr_name , cond , values = mo .group (1 ), mo .group (2 ), mo .group (3 )
205+ # Rally WSAPI supports attr_name in value1,value2,... directly but not so with !in
206+ if cond .lower () == '!in' : # we must construct an OR'ed express with != for each listed value
207+ # Rally WSAPI supports attr_name in value1,value2,... directly but not so with !in
208+ criteria = RallyQueryFormatter .constructORedExpression (attr_name , cond , values )
209+ elif mo := re .search (r'^(\w+) (!?between)\s+(.+)\s+and\s+(.+)$' , criteria , flags = re .I ):
210+ attr_name , cond , lesser , greater = mo .group (1 ), mo .group (2 ), mo .group (3 ), mo .group (4 )
211+ rlns = ['>=' , '<=' ] if cond .lower () == 'between' else ['<' , '>' ]
212+ rln_op = 'AND' if cond .lower () == 'between' else 'OR'
213+ lcond = '%s %s %s' % (attr_name , rlns [0 ], lesser )
214+ gcond = '%s %s %s' % (attr_name , rlns [1 ], greater )
215+ criteria = "(%s) %s (%s)" % (lcond , rln_op , gcond )
204216 expression = quote (criteria .strip ()).replace ('%28' , '(' ).replace ('%29' , ')' )
205217##
206218## print("RallyQueryFormatter.no_conjunctions: |%s|" % expression)
@@ -218,13 +230,12 @@ def _encode(condition):
218230 cond = quote (item )
219231 binary_expression = "(%s) %s" % (cond , binary_expression )
220232
221- final_expression = binary_expression .replace ('%28' , '(' )
222- final_expression = final_expression .replace ('%29' , ')' )
233+ encoded_parened_expression = binary_expression .replace ('%28' , '(' ).replace ('%29' , ')' )
223234##
224- ## print("RallyQueryFormatter.final_expression : |%s|" % final_expression )
225- ## print("==============================================")
235+ ## print("RallyQueryFormatter.encoded_parened_expression : |{0}|".format(encoded_parened_expression) )
236+ ## print("============================================================= ")
226237##
227- final_expression = final_expression .replace (' ' , '%20' )
238+ final_expression = encoded_parened_expression .replace (' ' , '%20' )
228239 return final_expression
229240
230241 @staticmethod
@@ -235,11 +246,37 @@ def validatePartsSyntax(parts):
235246 criteria_pattern = re .compile (r'^(%s) (%s) (%s)$' % (attr_ident , relationship , attr_value ))
236247 quoted_value_pattern = re .compile (r'^(%s) (%s) ("[^"]+")$' % (attr_ident , relationship ))
237248 unquoted_value_pattern = re .compile (r'^(%s) (%s) ([^"].+[^"])$' % (attr_ident , relationship ))
249+ subset_pattern = re .compile (r'(%s) (in|!in) (%s)' % (attr_ident , attr_value ), flags = re .I )
250+ range_pattern = re .compile (r'(%s) (between|!between) (%s) and (%s)' % (attr_ident , attr_value , attr_value ), flags = re .I )
238251
239252 valid_parts = []
240253 front = ""
241254 while parts :
242255 part = "%s%s" % (front , parts .pop (0 ))
256+ mo = subset_pattern .match (part )
257+ if mo :
258+ rln = '!=' if mo .group (1 ).startswith ('!' ) else '='
259+ attr_name , values = mo .group (0 ), mo .group (2 )
260+ # if rln == '=':
261+ # binary_expr = constructORedExpression(attr_name, rln, values)
262+ # else:
263+ # binary_expr = constructANDedExpression(attr_name, rln, values)
264+ valid_parts .append (binary_expr )
265+ continue
266+ mo = range_pattern .match (part )
267+ if mo :
268+ attr_name , cond = mo .group (0 ), mo .group (1 )
269+ lesser_val , greater_val = mo .group (2 ), mo .group (3 )
270+ rlns = ['<' , '>' ] if cond .startswith ('!' ) else ['>=' , '<=' ]
271+ # in range
272+ if cond .lower () == 'between' :
273+ binary_expr = '((%s %s %s) AND (%s %s %s))' % \
274+ (attr_name , rlns [0 ], lesser_val , attr_name , rlns [1 ], greater_val )
275+ else :
276+ binary_expr = '((%s %s %s) OR (%s %s %s))' % \
277+ (attr_name , rlns [0 ], lesser_val , attr_name , rlns [1 ], greater_val )
278+ valid_parts .append (binary_expr )
279+ continue
243280 mo = criteria_pattern .match (part )
244281 if mo :
245282 valid_parts .append (part )
@@ -260,4 +297,44 @@ def validatePartsSyntax(parts):
260297
261298 return valid_parts
262299
300+ # subset and range related ops for building queries
301+
302+ @staticmethod
303+ def constructORedExpression (field , relation , values ):
304+ """
305+ intended for use when a subset operator (in or !in) is in play
306+ State in Defined, Accepted, Relased
307+ needs an ORed expression ((f = D OR f = A) OR ((f = R)))
308+ State !in Working, Fixed, Testing
309+ needs an ANDed expression ((f != W AND f != F) AND ((f != T)))
310+ """
311+ operator = "="
312+ if relation == '!in' :
313+ operator = "!="
314+ if len (values ) == 1 :
315+ return f'({ field } { operator } "{ values [0 ]} )"'
316+ binary_expression = f'(({ field } { operator } "{ values [0 ]} ") OR ({ field } { operator } "{ values [1 ]} "))'
317+ for value in values [2 :]:
318+ binary_expression = f'({ binary_expression } OR ({ field } { operator } "{ value } "))'
319+ return binary_expression
320+
321+ @staticmethod
322+ def constructANDedExpression (field , relation , values ):
323+ """
324+ intended for use when a range operator (between or !between) is in play
325+ DevPhase between 2021-05-23 and 2021-07-09
326+ needs a single ANDed expression ((dp >= d1) AND (dp <= d1)))
327+ DevPhase !between 2021-12-19 and 2022-01-03
328+ needs a single ORed expression ((dp < d1) OR (dp > d1)))
329+ """
330+ operator = "="
331+ if relation == '!in' :
332+ operator = "!="
333+ if len (values ) == 1 :
334+ return f'({ field } { operator } "{ values [0 ]} )"'
335+ binary_expression = f'(({ field } { operator } "{ values [0 ]} ") AND ({ field } { operator } "{ values [1 ]} "))'
336+ for value in values [2 :]:
337+ binary_expression = f'({ binary_expression } AND ({ field } { operator } "{ value } "))'
338+ return binary_expression
339+
263340##################################################################################################
0 commit comments