{"record":{"id":"e4d973f7fef8be54","repo":"zylon-ai/private-gpt","slug":"invalid-call-statement-format","errorCode":null,"errorMessage":"Invalid CALL statement format","messagePattern":"Invalid CALL statement format","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/components/tabular/database_query_generator.py","lineNumber":1278,"sourceCode":"\n        except Exception as e:\n            return QueryResult(\n                query=call_statement,\n                error=ErrorQueryResult(\n                    description=f\"DB2 procedure execution failed: {mask_connection_secrets(str(e))}\",\n                    type=ErrorType.UNKNOWN,\n                ),\n                row_count=-1,\n            )\n\n    def _parse_call_statement(\n        self, call_statement: str\n    ) -> tuple[str, list[str | int], list[int]]:\n        match = re.search(\n            r\"CALL\\s+([\\w.]+)\\s*\\((.*?)\\)\", call_statement, re.IGNORECASE | re.DOTALL\n        )\n        if not match:\n            raise ValueError(\"Invalid CALL statement format\")\n\n        proc_name = match.group(1)\n        params_str = match.group(2)\n\n        param_values: list[str | int] = []\n        out_indices: list[int] = []\n\n        for i, param in enumerate(params_str.split(\",\")):\n            param = param.strip()\n            if param == \"?\":\n                param_values.append(0)  # Default value for OUT parameters\n                out_indices.append(i)\n            else:\n                param_values.append(param.strip(\"'\\\"\"))\n\n        return proc_name, param_values, out_indices\n\n    def _create_db2_connection(self) -> Any:","sourceCodeStart":1260,"sourceCodeEnd":1296,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/tabular/database_query_generator.py#L1260-L1296","documentation":"Thrown by _parse_call_statement in database_query_generator.py when the regex 'CALL\\s+([\\w.]+)\\s*\\((.*?)\\)' fails to match the query text. The generator only understands stored-procedure invocations of the exact form CALL schema.proc(args...), and re-parses the LLM-generated SQL to extract the procedure name, parameter values, and OUT-parameter positions. Any deviation in syntax (missing parens, empty CALL, malformed spacing that breaks the pattern) makes the parse fail and raises this ValueError.","triggerScenarios":"An LLM-generated query string passed to the row-count/result path (line 1239) does not literally start with or contain 'CALL proc(...)'. Examples: the model emits 'CALL proc' with no parentheses, wraps the call in BEGIN/COMMIT despite the prompt instruction, returns prose or an empty string, or uses a quoted procedure name like CALL \"my proc\"(...) whose spaces break the [\\w.]+ capture.","commonSituations":"Prompt-regression after changing the few-shot examples at line 773; models that add semicolons inside the parens or emit multiple statements; older stored procedures with uppercase/lowercase mix (handled) vs. quoted identifiers (not handled); an empty params string 'CALL p()' actually matches (group 2 is empty) but 'CALL p' does not.","solutions":["Log the exact call_statement that failed to parse and inspect it for syntax the regex cannot match (missing parens, quoted identifiers, BEGIN/COMMIT wrappers).","Tighten the prompt/few-shot at line 773 so the model emits exactly 'CALL schema.proc_name(arg1, ?, ?, ?);' with nothing else.","Normalize the statement before parsing: strip trailing semicolons, leading BEGIN/COMMIT, and collapse whitespace.","Extend the regex to allow quoted identifiers, e.g. r\"CALL\\s+(?:[\\w.]+|\\\"[^\\\"]+\\\")\\s*\\((.*?)\\)\".","Wrap the parse in a retry that asks the LLM to regenerate the CALL statement when a QuerySyntaxError/ValueError is raised."],"exampleFix":"# before\nmatch = re.search(\n    r\"CALL\\s+([\\w.]+)\\s*\\((.*?)\\)\", call_statement, re.IGNORECASE | re.DOTALL\n)\nif not match:\n    raise ValueError(\"Invalid CALL statement format\")\n\n# after\nnormalized = re.sub(r\"^(BEGIN|COMMIT)\\s*;?\\s*\", \"\", call_statement.strip(), flags=re.IGNORECASE).strip().rstrip(\";\").strip()\nmatch = re.search(\n    r\"CALL\\s+([\\w.]+)\\s*\\((.*?)\\)\", normalized, re.IGNORECASE | re.DOTALL\n)\nif not match:\n    logger.error(\"Unparseable CALL statement: %r\", call_statement)\n    raise ValueError(\"Invalid CALL statement format\")","handlingStrategy":"validation","validationCode":"import re\n\nCALL_RE = re.compile(r\"CALL\\s+([\\w.]+)\\s*\\((.*?)\\)\", re.IGNORECASE | re.DOTALL)\n\ndef is_parseable_call(stmt: str) -> bool:\n    stmt = stmt.strip().rstrip(';').strip()\n    return bool(CALL_RE.search(stmt))\n\n# before the generator call:\n# assert is_parseable_call(llm_query), f\"bad CALL statement: {llm_query!r}\"","typeGuard":null,"tryCatchPattern":"try:\n    row_count = generator.get_row_count(result)\nexcept ValueError as e:\n    if \"Invalid CALL statement format\" in str(e):\n        # regenerate the SQL with corrective feedback\n        result = regenerate_sql(prompt_with_error(str(e)))\n    else:\n        raise","preventionTips":["Pin few-shot examples so the model always emits 'CALL schema.proc(arg, ?, ?, ?);'","Normalize whitespace/semicolons and strip BEGIN/COMMIT before parsing","Reject quoted procedure identifiers at generation time or extend the regex deliberately"],"tags":["sql","regex","llm-output","stored-procedures","parsing"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}