Commits

Olemis Lang  committed 4f8eec5

GViz QL [ refs dataviz-299 ] : Introduce {{{GVIzQLClauseHandler.configure}}} method to support OPTION clause

- Store options influencing the behavior of FORMAT clause
- Sanitize the sequence of options should it contain dictionaries (i.e. columns definitions)
- Expected data sets in test cases for OPTION clause

  • Participants
  • Parent commits 4003199

Comments (0)

Files changed (2)

File trac-dev/gviz/tracgviz/gvizql.py

     msg = self.ERR_MSG % vals
     raise GVizUnsupportedQueryOp(msg)
 
+  def configure(self, options):
+    r"""Configure this clause handler according to `options`
+    """
+    raise NotImplementedError("Must implement abstract method 'configure'")
+
   def transform(self, schema, data):
     r"""Transform the result set as dictated by the clause included 
     in the GViz QL expression provided by the client. This 
     # Activate select clause. May be turned off e.g. by PIVOT clause
     self.active = True
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Arrange and transform values considering the given expressions list.
     """
     # FIXME: str(colnm) => colnm.encode('utf-8')
     self.basetable = name['schema'](lambda colnm: (str(colnm), None))[0]
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Notify that base table has not been processed by data source. 
     This is made this way since it may hide a bug in the underlying 
     # FIXME: str(colnm) => colnm.encode('utf-8')
     self.filter = self._resolve_schema(expr, lambda colnm: (str(colnm), None))[0]
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Filter rows that do not satisfy target predicate.
     """
     self.aggregate = [self._resolve_schema(c, lambda colnm: (str(colnm), None))[0] 
         for c in self.groups_def ]
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Create groups.
     """
 
   MSG_NOAGG = 'Cannot use PIVOT when no aggregations are defined in SELECT.'
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Transpose tha data table considering the details of SELECT clause
     """
         (self._resolve_schema(c, lambda colnm: (colnm.encode('utf-8'), None))[0],
          is_asc) for c, is_asc in self.order_def ]
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Sort rows in base result set by evaluating target expressions.
     """
     """
     self.cnt = number
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Retrieve no more than `cnt` items.
     """
     """
     self.skip = number
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""Skip the number of items determined by `skip` attribute.
     """
       seq = Sequence([seq])
     self.labels = seq
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
     r"""(Add | modify) the schema in order to (include | update) 
     column labels.
     if not isinstance(seq, Sequence):
       seq = Sequence([seq])
     self.fmt = seq
+    self.no_values = self.no_format = False
+
+  def configure(self, options):
+    r"""Store formatting options
+    """
+    self.no_values = 'no_values' in options
+    self.no_format = 'no_format' in options
 
   DATETIME_CONVERTER = {
       'date' : lambda x : datetime.combine(x, time()),
   KEYWORDS = ('options',)
 
   def __init__(self, seq):
-    r"""Initialize this clause with a list of option names.
+    r"""Initialize this clause with a list of (option name, value) tuples.
     """
-    if not isinstance(seq, Sequence):
-      get_col_schema = lambda _colnm: (_colnm.encode('utf-8'), None)
-      seq = seq['schema'](get_col_schema)[0]
+    get_col_schema = lambda _colnm: (_colnm.encode('utf-8'), None)
+    if isinstance(seq, dict):
+      seq = Sequence([(seq['schema'](get_col_schema)[0], None)])
+    elif isinstance(seq, basestring):
+      seq = Sequence([(seq, None)])
+    elif isinstance(seq, tuple):
       seq = Sequence([seq])
+    elif isinstance(seq, Sequence):
+      seq = Sequence([x if not isinstance(x, dict)
+                        else (x['schema'](get_col_schema)[0], None)
+                      for x in seq])
+
+    # Validate options
+    has_no_format = has_no_values = False
+    for o,v in seq:
+      has_no_format = has_no_format or o == 'no_format'
+      has_no_values = has_no_values or o == 'no_values'
+      if has_no_format and has_no_values:
+        raise GVizInvalidQuery('Contradictory options no_value and no_format')
     self.opts = seq
 
+  def configure(self, options):
+    r"""Ignore configuration options"""
+
   def transform(self, schema, data):
-    r"""Condition the result set according to options.
+    r"""No transformations applied
     """
-    self.unsupported()
+    return schema, data
 
 #------------------------------------------------------
 #   GVizQL parsing and compilation
     r"""Transform the result set as determined by the GVizQL 
     expression directives and clauses.
     """
+    options = getattr(self, 'opts', None)
     for ch in self.itereval():
+      if options:
+        ch.configure(options)
       schema, data = ch.transform(schema, data)
     return schema, data
 
       colseq = self.Sequence([colseq, colnm])
     return colseq
 
-  handle_optname = handle_column
+  def handle_optname(self, optnm):
+    return (optnm[1], None)
 
   def handle_optval(self, optnm, _, value):
-    get_col_schema = lambda _colnm: (_colnm.encode('utf-8'), None)
-    optnm = self.handle_column(optnm)
-    optnm = colnm['schema'](get_col_schema)[0]
-    return (optnm, literal_eval(value[1]))
+    return (optnm[1], literal_eval(value[1]))
 
   def handle_optseq(self, optseq, opt):
     optseq = optseq[1]
     opt = opt[1]
     get_col_schema = lambda _colnm: (_colnm.encode('utf-8'), None)
     if isinstance(optseq, self.Sequence):
-      if isinstance(opt, tuple):
-        optseq.append(opt)
-      else:
-        opt = colnm['schema'](get_col_schema)[0]
+      if not isinstance(opt, tuple):
+        opt = (opt, None)
       optseq.append((optnm, None))
     else:
       if not isinstance(optseq, tuple):
-          optseq = (self.handle_optname((None, optseq)), None)
+          optseq = (optseq, None)
       if not isinstance(opt, tuple):
-          opt = (self.handle_optname((None, opt)), None)
+          opt = (opt, None)
       optseq = self.Sequence([optseq, opt])
     return colseq
 

File trac-dev/gviz/tracgviz/testing/test_gvizql.py

          lunchTime = 13:00:00
 
       """,
-  'Parsing OPTIONS extensions' : r"""
-      >>> parse("  select dayOfWeek(hireDate) "
-      ...       "format `dayOfWeek(hireDate)` 'Error;Sun;Mon;Tue;Wed;Thu;Fri;Sat;Error'"
-      ...       "  options    format_engine:'hireDate:NumberRule'   ", 'opts', 'cols')
-
-      """,
   'Parsing FORMAT (complex)' : r"""
       >>> parse(r'''select max(salary) label `max(salary)` 'Better paid' 
       ...       format `max(salary)` "#,##0.00" ''',
       *****
       * Parsing
       *****
-      ['no_format']
+      [('no_format', None)]
       None
       *****
       * Result
       *****
-      GVizUnsupportedQueryOp  :  Unable to evaluate OPTIONS clause. Either the whole clause or an specific feature is not supported yet.
+      = Columns =
+      name string
+      dept string
+      lunchTime timeofday
+      salary number
+      hireDate date
+      age number
+      isSenior boolean
+      seniorityStartTime datetime
+      = Row =
+         name = John
+         dept = Eng
+         lunchTime = 12:00:00
+         salary = 1000
+         hireDate = 2005-03-19
+         age = 35
+         isSenior = True
+         seniorityStartTime = 2007-12-02 15:56:00
+      = Row =
+         name = Dave
+         dept = Eng
+         lunchTime = 12:00:00
+         salary = 500
+         hireDate = 2006-04-19
+         age = 27
+         isSenior = False
+         seniorityStartTime = None
+      = Row =
+         name = Sally
+         dept = Eng
+         lunchTime = 13:00:00
+         salary = 600
+         hireDate = 2005-10-10
+         age = 30
+         isSenior = False
+         seniorityStartTime = None
+      = Row =
+         name = Ben
+         dept = Sales
+         lunchTime = 12:00:00
+         salary = 400
+         hireDate = 2002-10-10
+         age = 32
+         isSenior = True
+         seniorityStartTime = 2005-03-09 12:30:00
+      = Row =
+         name = Dana
+         dept = Sales
+         lunchTime = 12:00:00
+         salary = 350
+         hireDate = 2004-09-08
+         age = 25
+         isSenior = False
+         seniorityStartTime = None
+      = Row =
+         name = Mike
+         dept = Marketing
+         lunchTime = 13:00:00
+         salary = 800
+         hireDate = 2005-01-10
+         age = 24
+         isSenior = True
+         seniorityStartTime = 2007-12-30 14:40:00
 
 
       >>> parse("select dept , salary options no_values", 'opts', 'cols')
       *****
       * Parsing
       *****
-      ['no_values']
+      [('no_values', None)]
       ['dept', 'salary']
       *****
       * Result
       *****
-      GVizUnsupportedQueryOp  :  Unable to evaluate OPTIONS clause. Either the whole clause or an specific feature is not supported yet.
+      = Columns =
+      dept string
+      salary number
+      = Row =
+         dept = (None, 'Eng')
+         salary = (None, '1000')
+      = Row =
+         dept = (None, 'Eng')
+         salary = (None, '500')
+      = Row =
+         dept = (None, 'Eng')
+         salary = (None, '600')
+      = Row =
+         dept = (None, 'Sales')
+         salary = (None, '400')
+      = Row =
+         dept = (None, 'Sales')
+         salary = (None, '350')
+      = Row =
+         dept = (None, 'Marketing')
+         salary = (None, '800')
 
 
       >>> parse("select `email address`, name, `date` options no_values", \
       *****
       * Parsing
       *****
-      ['no_values']
+      [('no_values', None)]
       ['email address', 'name', 'date']
       *****
       * Result
       *****
       GVizUnknownColumn  :  Unknown column email address.
 
+
+      >>> parse("  select *  options    no_format , no_values  ", 'opts', 'cols')
+      *****
+      * Tokens
+      *****
+      Token.Keyword.Reserved select
+      Token.Name.Other *
+      Token.Keyword.Reserved options
+      Token.Name.Variable no_format
+      Token.Punctuation ,
+      Token.Name.Variable no_values
+      *****
+      * Parsing
+      *****
+      Contradictory options no_value and no_format
+
+      """,
+  'Parsing OPTIONS extensions' : r"""
+      >>> parse("  select dayOfWeek(hireDate) "
+      ...       "format `dayOfWeek(hireDate)` 'Error;Sun;Mon;Tue;Wed;Thu;Fri;Sat;Error'"
+      ...       "  options    format_engine:'`dayOfWeek(hireDate)`:InlineNumberRule'   ", 
+      ...       'opts', 'cols', 'fmt')
+      *****
+      * Tokens
+      *****
+      Token.Keyword.Reserved select
+      Token.Name.Function dayOfWeek
+      Token.Punctuation (
+      Token.Name.Variable hireDate
+      Token.Punctuation )
+      Token.Keyword.Reserved format
+      Token.Name.Variable `dayOfWeek(hireDate)`
+      Token.Literal.String.Single 'Error;Sun;Mon;Tue;Wed;Thu;Fri;Sat;Error'
+      Token.Keyword.Reserved options
+      Token.Name.Variable format_engine
+      Token.Punctuation :
+      Token.Literal.String.Single '`dayOfWeek(hireDate)`:InlineNumberRule'
+      *****
+      * Parsing
+      *****
+      [(u'format_engine', '`dayOfWeek(hireDate)`:InlineNumberRule')]
+      ['dayOfWeek(hireDate)']
+      [('dayOfWeek(hireDate)', 'Error;Sun;Mon;Tue;Wed;Thu;Fri;Sat;Error')]
+      *****
+      * Result
+      *****
+
+
+      >>> parse("select * options no_format , no_values , "
+      ...       "format_engine:'salary:NumberRule'", 'opts', 'cols')
+      *****
+      * Tokens
+      *****
+      Token.Keyword.Reserved select
+      Token.Name.Other *
+      Token.Keyword.Reserved options
+      Token.Name.Variable no_format
+      Token.Punctuation ,
+      Token.Name.Variable no_values
+      Token.Punctuation ,
+      Token.Name.Variable format_engine
+      Token.Punctuation :
+      Token.Literal.String.Single 'salary:NumberRule'
+      *****
+      * Parsing
+      *****
+      Contradictory options no_value and no_format
+
       """,
     'Parsing (failures)' : r"""
       >>> parse("  select ", 'cols', fail=True)