Skip to content

parse_msql: UnboundLocalError on 3+ juxtaposed conditions (statement() branches on len(items) for 1-3 only) #262

Description

@hsingh84ai

parse_msql raises UnboundLocalError on three or more juxtaposed conditions.

The grammar makes AND optional — statement uses wherefullcondition+, a repetition — so conditions can be juxtaposed. That works for one or two, then breaks.

Repro

from massql.msql_parser import parse_msql
parse_msql("QUERY scaninfo(MS2DATA) WHERE MS2PROD=100 MS2PROD=200 MS2PROD=300")
lark.exceptions.VisitError: Error trying to process rule "statement":
cannot access local variable 'query_dict' where it is not associated with a value

Two juxtaposed conditions are fine, so the boundary is exactly at three:

parse_msql("QUERY scaninfo(MS1DATA) WHERE RTMIN=5 RTMAX=10")            # ok
parse_msql("QUERY scaninfo(MS1DATA) WHERE RTMIN=5 RTMAX=10 CHARGE=1")   # raises

WHERE a b FILTER c raises for the same reason — it also produces four top-level items:

parse_msql("QUERY scaninfo(MS2DATA) WHERE MS2PROD=100 MS2PROD=200 FILTER MS2PROD=300")  # raises

Mixing forms is fine as long as the count stays at three, because a AND b collapses into one item:

parse_msql("QUERY scaninfo(MS2DATA) WHERE MS2PROD=100 MS2PROD=200 AND MS2PROD=300")  # ok

Cause

MassQLToJSON.statement() (massql/msql_parser.py:327) branches on len(items) for 1, 2 and 3 only, and assigns query_dict inside each branch:

def statement(self, items):
   items = [item for item in items if item != "WHERE"]
   items = [item for item in items if item != "QUERY"]

   if len(items) == 1: ...
   if len(items) == 2: ...
   if len(items) == 3: ...

   return query_dict

A statement with four or more top-level items (querytype + three juxtaposed conditions, or querytype + two juxtaposed + a filter list) matches no branch and falls through to return query_dict with the local unassigned.

Suggested fix

The three branches already do the same thing — take items[0] as the querytype and concatenate everything after it — so they collapse into the general case:

def statement(self, items):
   items = [item for item in items if item not in ("WHERE", "QUERY")]

   query_dict = {"querytype": items[0], "conditions": []}
   for group in items[1:]:
      query_dict["conditions"] += group

   return query_dict

That reproduces the current behaviour for 1, 2 and 3 items and handles the rest. If juxtaposition past two is meant to be rejected rather than supported, an explicit error would still be better than UnboundLocalError, since the query parses cleanly and only fails in the transformer.

Context

Found while porting the parser and IR transformer to TypeScript for a browser tool. Checked against 17e8c7496df0188bc2751e13701faf06284266b7.

How often it fires, in case it helps with prioritisation: across 1,516 gold queries drawn from the community compendium, upstream's test suite and logged user requests, zero are in this form. Across 8,618 saved model-generated queries, 8 are (0.09%). It is a sharp edge rather than a common crash — the exposure is that 29% of those golds have three or more conditions, so anything that emits juxtaposition hits it immediately.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions