vishalmore90 opened a new issue, #41288:
URL: https://github.com/apache/beam/issues/41288

   
   
   ### What happened?
   
   In Beam YAML, SQL-backed aggregations are supported via both `language: sql` 
and `language: calcite` (as registered by `SqlBackedProvider` in 
`apache_beam/yaml/yaml_combine.py` and documented in 
`documentation/sdks/yaml-combine.md`).
   
   However, `normalize_combine` in 
`sdks/python/apache_beam/yaml/yaml_combine.py` contains a language-check flaw:
   
   ```python
       def normalize_agg(dest, agg):
         if isinstance(agg, str):
           agg = {'fn': agg}
         if 'value' not in agg and config.get('language') != 'sql':
           agg['value'] = dest
         if isinstance(agg['fn'], str):
           agg['fn'] = {'type': agg['fn']}
         return agg
   ```
   
   This causes two distinct query generation failures:
   
   #### 1. `language: calcite` with SQL expressions produces `sum(col)(dest) as 
dest`
   According to the documentation for SQL-style aggregations, users specify SQL 
snippets as the combine fn:
   ```yaml
   type: Combine
   config:
     language: calcite
     group_by: col1
     combine:
       total: "sum(col2)"
       num_values: "count(*)"
   ```
   Because `config.get('language') != 'sql'` evaluates to `True` for 
`'calcite'`, `agg['value'] = dest` is mistakenly executed, setting 
`agg['value'] = 'total'`.
   Then in `_SqlCombineTransform`:
   ```python
     def combine_col(dest, fn_spec):
       if 'value' in fn_spec or 'config' in fn_spec['fn']:
         expr = '%s(%s)' % (
             fn_spec['fn']['type'],
             ', '.join([fn_spec['value']] +
                       list(fn_spec['fn'].get('config', {}).values())))
       else:
         expr = fn_spec['fn']['type']
       return f'{expr} as {dest}'
   ```
   Because `'value'` is present in `fn_spec`, it constructs:
   - `expr = 'sum(col2)(total)'`
   - `expr = 'count(*)(num_values)'`
   
   Generating the invalid SQL query:
   ```sql
   SELECT col1, sum(col2)(total) as total, count(*)(num_values) as num_values 
FROM PCOLLECTION GROUP BY col1
   ```
   Calcite's SQL parser rejects this with `SqlParseException: Encountered "("`.
   
   #### 2. `language: sql` with shorthand column aggregations produces `sum as 
col2`
   According to the documentation, shorthand aggregation where destination is 
the input column is supported (e.g. `col2: sum`).
   When specified under `language: sql`:
   ```yaml
   type: Combine
   config:
     language: sql
     group_by: col1
     combine:
       col2: sum
   ```
   Because `config.get('language') != 'sql'` evaluates to `False`, 
`agg['value'] = dest` is skipped.
   `_SqlCombineTransform` receives `fn_spec = {'fn': {'type': 'sum'}}` without 
`'value'`. It falls back to `expr = fn_spec['fn']['type']` (`'sum'`), 
generating:
   ```sql
   SELECT col1, sum as col2 FROM PCOLLECTION GROUP BY col1
   ```
   Which is also completely invalid SQL syntax (`sum as col2` instead of 
`sum(col2) as col2`).
   
   ---
   
   ### Steps to Reproduce
   
   Run the following minimal script against `master`:
   
   ```python
   import apache_beam as beam
   from apache_beam.yaml.yaml_combine import normalize_combine, 
_SqlCombineTransform
   
   pcoll = beam.Pipeline() | beam.Create([beam.Row(col1='a', col2=10)])
   
   # Case 1: language: calcite with SQL expressions
   spec1 = {
       'type': 'Combine',
       'config': {
           'language': 'calcite',
           'group_by': ['col1'],
           'combine': {
               'total': 'sum(col2)',
               'num_values': 'count(*)'
           }
       }
   }
   queries1 = []
   _ = pcoll | _SqlCombineTransform(
       lambda q: queries1.append(q) or beam.Map(lambda x: x),
       **normalize_combine(spec1)['config'])
   print("Case 1 (calcite):", queries1[-1])
   # Output: SELECT col1, sum(col2)(total) as total, count(*)(num_values) as 
num_values FROM PCOLLECTION GROUP BY col1
   
   # Case 2: language: sql with shorthand
   spec2 = {
       'type': 'Combine',
       'config': {
           'language': 'sql',
           'group_by': ['col1'],
           'combine': {
               'col2': 'sum'
           }
       }
   }
   queries2 = []
   _ = pcoll | _SqlCombineTransform(
       lambda q: queries2.append(q) or beam.Map(lambda x: x),
       **normalize_combine(spec2)['config'])
   print("Case 2 (sql):", queries2[-1])
   # Output: SELECT col1, sum as col2 FROM PCOLLECTION GROUP BY col1
   ```
   
   ---
   
   ### Proposed Solution
   
   In `sdks/python/apache_beam/yaml/yaml_combine.py`:
   1. Check whether the language is a SQL dialect (`language in ('sql', 
'calcite')`).
   2. Only suppress defaulting `agg['value'] = dest` when the provided 
aggregation snippet is already a call expression (e.g., contains `(`):
   
   ```python
       def normalize_agg(dest, agg):
         if isinstance(agg, str):
           agg = {'fn': agg}
         is_sql = config.get('language') in ('sql', 'calcite')
         fn_type = (agg.get('fn') if isinstance(agg.get('fn'), str)
                    else agg.get('fn', {}).get('type', ''))
         if 'value' not in agg:
           if not (is_sql and '(' in fn_type):
             agg['value'] = dest
         if isinstance(agg['fn'], str):
           agg['fn'] = {'type': agg['fn']}
         return agg
   ```
   
   This properly generates:
   - `SELECT col1, sum(col2) as total, count(*) as num_values FROM PCOLLECTION 
GROUP BY col1` for both `calcite` and `sql` expressions.
   - `SELECT col1, sum(col2) as col2 FROM PCOLLECTION GROUP BY col1` for 
shorthand function names in both dialects.
   
   ---
   
   ### Issue Priority
   
   Priority: 2 (default / most bugs should be filed as P2)
   
   ---
   
   ### Issue Components
   
   - [x] Component: Python SDK
   - [x] Component: Beam YAML
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to