{"record":{"id":"f07c87f133e2b190","repo":"googleapis/mcp-toolbox","slug":"unclosed-subquery-parenthesis","errorCode":null,"errorMessage":"unclosed subquery parenthesis","messagePattern":"unclosed subquery parenthesis","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/tools/bigquery/bigquerycommon/table_name_parser.go","lineNumber":394,"sourceCode":"\t\tcase stateInRawTripleSingleQuoteString:\n\t\t\tif strings.HasPrefix(remaining, \"'''\") {\n\t\t\t\tstate = stateNormal\n\t\t\t\ti += 3\n\t\t\t} else {\n\t\t\t\ti++\n\t\t\t}\n\t\tcase stateInRawTripleDoubleQuoteString:\n\t\t\tif strings.HasPrefix(remaining, `\"\"\"`) {\n\t\t\t\tstate = stateNormal\n\t\t\t\ti += 3\n\t\t\t} else {\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t}\n\n\tif inSubquery {\n\t\treturn 0, fmt.Errorf(\"unclosed subquery parenthesis\")\n\t}\n\treturn len(sql), nil\n}\n\n// parseIdentifierSequence parses a sequence of dot-separated identifiers.\n// It returns the parts of the identifier, the number of characters consumed, and an error.\nfunc parseIdentifierSequence(s string) ([]string, int, error) {\n\tvar parts []string\n\tvar totalConsumed int\n\n\tfor {\n\t\tremaining := s[totalConsumed:]\n\t\ttrimmed := strings.TrimLeftFunc(remaining, unicode.IsSpace)\n\t\ttotalConsumed += len(remaining) - len(trimmed)\n\t\tcurrent := s[totalConsumed:]\n\n\t\tif len(current) == 0 {\n\t\t\tbreak","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/bigquery/bigquerycommon/table_name_parser.go#L376-L412","documentation":"parseSQL tracks subquery parentheses while scanning the statement. If the scan ends while still inside a subquery (inSubquery is true), the SQL is malformed — a '(' opened a subquery that was never closed — so parsing cannot complete and the parser returns this error.","triggerScenarios":"Calling parseSQL/TableParser on SQL with an unmatched '(' inside the query, e.g. a truncated query or a missing closing parenthesis after a subquery or IN (...) list.","commonSituations":"Hand-written or templated SQL with mismatched parens; string concatenation that drops the tail of a query; copy-paste truncation; code generators producing broken subqueries.","solutions":["Count and balance all parentheses in the query; add the missing ')'","Print/format the SQL before parsing to spot the unbalanced section","If the SQL is built dynamically, fix the template so subqueries are always fully closed","Validate the SQL in the BigQuery console / dry run to locate the syntax error"],"exampleFix":"// before\nSELECT * FROM proj.ds.t WHERE id IN (SELECT id FROM proj.ds.other;\n// after\nSELECT * FROM proj.ds.t WHERE id IN (SELECT id FROM proj.ds.other);","handlingStrategy":"validation","validationCode":"func balancedParens(sql string) bool {\n    depth := 0\n    inStr, inBacktick := false, false\n    for _, r := range sql {\n        switch {\n        case r == '`': inBacktick = !inBacktick\n        case r == '\\'' && !inBacktick: inStr = !inStr\n        case !inStr && !inBacktick && r == '(': depth++\n        case !inStr && !inBacktick && r == ')': depth--\n        }\n        if depth < 0 { return false }\n    }\n    return depth == 0\n}","typeGuard":null,"tryCatchPattern":"if !balancedParens(sql) {\n    return fmt.Errorf(\"query has unbalanced parentheses; check subqueries\")\n}\n_, err := parser.Parse(sql)\nif err != nil {\n    return fmt.Errorf(\"parse failed: %w\", err)\n}","preventionTips":["Use an SQL formatter/linter to check paren balance before submitting","Avoid hand-building SQL via string concatenation; use parameterized builders","Test templated queries with representative inputs"],"tags":["bigquery","sql-parsing","syntax-error"],"backgroundTag":"unbalanced-parentheses","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}