feat(gaussdb): enhance PL/SQL procedural language support (closes #41) - #42
Conversation
P0: Cross-statement plain metavar unification - Include plain metavars (bind_attr=None) in _ogsql_bind for post-match verification, enabling cross-statement consistency checks. - Extend verify_metavar_consistency with AST-structure-based counting for plain metavars. - Include plain metavar bound values in bind_metavars_with_backtrack intersection computation. P1: Control flow statement body recursion - IF/CASE/LOOP/WHILE/FOR/FOREACH now recursively convert body statements to child UniversalNodes, enabling pattern matching on statements inside loops/conditionals. - Mirror convert_pl_block recursion pattern. P2: Clause-level AST children for DML statements - SELECT: add SELECT_LIST, FROM_CLAUSE, GROUP_BY, HAVING_CLAUSE, ORDER_BY children with structured sub-nodes. - INSERT VALUES: wrap in VALUES_CLAUSE with VALUES_ROW children. - UPDATE SET: wrap assignments in SET_CLAUSE. - Enables $... wildcard scoping within individual SQL clauses. P3: Cursor declaration scrollable metadata - Attach scrollable attribute to CURSOR declaration nodes. Fix: Route PL/pgSQL control flow keywords to ogsql parser - Add IF, FOR, LOOP, WHILE, CASE, CURSOR, END IF, END LOOP, END CASE as ogsql routing triggers in pattern_tree.rs. - Without this, patterns like FOR $VAR IN (...) LOOP were incorrectly routed to tree-sitter-sequel. Regression tests: 8 rules + 13 cases (13/13 PASS)
📝 WalkthroughWalkthroughOGSQL conversion now emits structured SQL and PL/pgSQL AST nodes, pattern routing recognizes more control-flow forms, plain metavariable consistency is enforced during backtracking, and GaussDB regression rules and cases cover clauses, table unification, and cursors. ChangesOGSQL parsing and matching
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant PatternTreeParser
participant OgsqlAdapter
participant UniversalNode
participant TreeMatcher
PatternTreeParser->>OgsqlAdapter: route SQL or PL/pgSQL pattern
OgsqlAdapter->>UniversalNode: build structured AST nodes
UniversalNode-->>PatternTreeParser: return parsed pattern tree
PatternTreeParser->>TreeMatcher: provide metavariables and pattern structure
TreeMatcher->>TreeMatcher: validate bindings during backtracking
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
crates/astgrep-parser/src/adapter/ogsql/pl.rs (1)
130-168: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
IF/CASEelse_stmtsare flattened as direct children, indistinguishable from THEN/WHEN bodies.ELSIF gets an
elsif_branchwrapper and CASEwhensgetcase_whenwrappers, butelse_stmtsare appended directly onto theif_statement/case_statementnode (Lines 145-147, 164-166). A matcher can't tell THEN/WHEN statements apart from ELSE statements. Consider wrapping ELSE bodies in anelse_branchnode for symmetry and unambiguous matching.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/astgrep-parser/src/adapter/ogsql/pl.rs` around lines 130 - 168, Wrap each `else_stmts` collection in an `else_branch` AST node before adding it to the parent in the `PlStatement::If` and `PlStatement::Case` arms. Convert and append the statements to that wrapper, then add the wrapper to the `if_statement` or `case_statement`, preserving existing span propagation and error handling.crates/astgrep-parser/src/pattern_tree.rs (1)
211-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated PL/pgSQL keyword detection into a shared helper.
Both sites encode the same control-flow keyword set, and they have already drifted (the retry list omits
@andCURSOR). Keeping two hand-maintained lists invites future inconsistency in routing decisions. Factor the shared predicate (e.g.fn looks_like_plpgsql(s: &str) -> bool) so both call sites stay in sync.
crates/astgrep-parser/src/pattern_tree.rs#L211-L222: replace the inlineneeds_ogsqlkeyword chain with a call to the shared helper.crates/astgrep-parser/src/pattern_tree.rs#L271-L280: replace the retry keyword chain with the same helper, keeping any intentional@/CURSORdifferences explicit and documented.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/astgrep-parser/src/pattern_tree.rs` around lines 211 - 222, Extract the shared PL/pgSQL detection predicate into a helper such as looks_like_plpgsql, then replace the inline needs_ogsql chain at crates/astgrep-parser/src/pattern_tree.rs:211-222 and the retry keyword chain at crates/astgrep-parser/src/pattern_tree.rs:271-280 with that helper. Ensure both sites use the same keyword set, or keep any intentional `@/CURSOR` differences explicit and documented.tests/categories/sql_dialects/gaussdb/rules/regression_issue41.yaml (1)
4-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for the advertised
opengaussdialect.Line 5 declares support for both
gaussdbandopengauss, but the supplied fixtures and validation path exercise only--dialect gaussdb. Add equivalent OpenGauss coverage or removeopengaussuntil it is verified.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/categories/sql_dialects/gaussdb/rules/regression_issue41.yaml` around lines 4 - 5, Add regression coverage for the advertised opengauss dialect in the fixture and validation flow associated with regression_issue41, ensuring it is actually exercised with --dialect opengauss; otherwise remove opengauss from the dialect declaration until that coverage is available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/astgrep-matcher/src/tree_matcher.rs`:
- Around line 2390-2397: Update the candidate construction in the tree matcher’s
PatternTree::Metavar handling to preserve the absence of a bind attribute as
Option<&str> rather than converting it to "". Adjust the later uniqueness check
so only candidates with Some attributes participate in the same-bind-attribute
difference rule, allowing distinct plain metavars with identical valid text.
- Around line 2191-2195: Update the metavar matching logic around
collect_ogsql_metavars and the plain-metavar verification near the documented
occurrence-count behavior to preserve repeated (name, None) occurrences before
deduplication. Carry each plain metavar’s expected occurrence count separately
from its deduplicated binding candidates, or derive counts from the complete
pattern tree, so repeated metavars require all occurrences while distinct
bindings remain deduplicated.
In `@crates/astgrep-parser/src/adapter/ogsql/pl.rs`:
- Around line 192-203: Update the PlStatement::For conversion to add inner.kind
as a child of the “for_statement” node, preserving its source structure so query
and range expressions remain visible alongside the existing label, variable, and
body children.
In `@crates/astgrep-parser/src/pattern_tree.rs`:
- Around line 211-222: Update the needs_ogsql detection in pattern parsing to
normalize the trimmed pattern once for case-insensitive keyword checks, while
preserving the symbol and syntax checks. Apply the same normalized matching
logic to the retry branch before parse_ogsql so lowercase and mixed-case
PL/pgSQL keywords take the ogsql path.
---
Nitpick comments:
In `@crates/astgrep-parser/src/adapter/ogsql/pl.rs`:
- Around line 130-168: Wrap each `else_stmts` collection in an `else_branch` AST
node before adding it to the parent in the `PlStatement::If` and
`PlStatement::Case` arms. Convert and append the statements to that wrapper,
then add the wrapper to the `if_statement` or `case_statement`, preserving
existing span propagation and error handling.
In `@crates/astgrep-parser/src/pattern_tree.rs`:
- Around line 211-222: Extract the shared PL/pgSQL detection predicate into a
helper such as looks_like_plpgsql, then replace the inline needs_ogsql chain at
crates/astgrep-parser/src/pattern_tree.rs:211-222 and the retry keyword chain at
crates/astgrep-parser/src/pattern_tree.rs:271-280 with that helper. Ensure both
sites use the same keyword set, or keep any intentional `@/CURSOR` differences
explicit and documented.
In `@tests/categories/sql_dialects/gaussdb/rules/regression_issue41.yaml`:
- Around line 4-5: Add regression coverage for the advertised opengauss dialect
in the fixture and validation flow associated with regression_issue41, ensuring
it is actually exercised with --dialect opengauss; otherwise remove opengauss
from the dialect declaration until that coverage is available.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 65c3416d-7ede-4de0-8b5e-53c78564e0ee
📒 Files selected for processing (19)
crates/astgrep-matcher/src/tree_matcher.rscrates/astgrep-parser/src/adapter/ogsql/dml.rscrates/astgrep-parser/src/adapter/ogsql/expr.rscrates/astgrep-parser/src/adapter/ogsql/pl.rscrates/astgrep-parser/src/pattern_tree.rstests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_group_by.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_group_by_multi.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_having.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_having_agg.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_order_by.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_order_by_desc.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_multi_col.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_no_where.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_single_col.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_where.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/cursor_decl.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/unify_table_match.sqltests/categories/sql_dialects/gaussdb/cases/regression_issue41/unify_table_mismatch.sqltests/categories/sql_dialects/gaussdb/rules/regression_issue41.yaml
| /// For plain metavars (bind_attr=None), verifies that the bound value | ||
| /// appears at least `expected_count` times in the target AST subtree's | ||
| /// text nodes. This uses AST-structure-based counting rather than raw | ||
| /// text search — only node text values are checked, not arbitrary | ||
| /// substrings. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Preserve metavar occurrence counts before deduplication.
collect_ogsql_metavars deduplicates (name, bind_attr) pairs upstream, so repeated plain metavars arrive here only once. expected_count is therefore always 1 for a repeated (name, None) pair, allowing missing repeated occurrences to pass. Carry occurrence counts separately from binding candidates, or derive them from the full pattern tree.
Also applies to: 2234-2249
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/astgrep-matcher/src/tree_matcher.rs` around lines 2191 - 2195, Update
the metavar matching logic around collect_ogsql_metavars and the plain-metavar
verification near the documented occurrence-count behavior to preserve repeated
(name, None) occurrences before deduplication. Carry each plain metavar’s
expected occurrence count separately from its deduplicated binding candidates,
or derive counts from the complete pattern tree, so repeated metavars require
all occurrences while distinct bindings remain deduplicated.
| } else if let PatternTree::Metavar { | ||
| name, | ||
| bind_attr: None, | ||
| } = mv | ||
| { | ||
| if let Some(bound) = self.bindings.get(name) { | ||
| all_candidates.push((name, "", vec![bound.clone()])); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not map all plain metavars to the empty attribute name.
The later uniqueness check treats every "" attribute as shared, so two distinct plain metavars bound to the same valid text are rejected. Retain Option<&str> for the candidate attribute and exclude None entries from the “same bind attribute must differ” rule.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/astgrep-matcher/src/tree_matcher.rs` around lines 2390 - 2397, Update
the candidate construction in the tree matcher’s PatternTree::Metavar handling
to preserve the absence of a bind attribute as Option<&str> rather than
converting it to "". Adjust the later uniqueness check so only candidates with
Some attributes participate in the same-bind-attribute difference rule, allowing
distinct plain metavars with identical valid text.
| PlStatement::For(inner) => { | ||
| let span = inner.span.clone().or_else(|| parent_span.cloned()); | ||
| Ok(apply_span( | ||
| AstBuilder::sql_expression("for_statement"), | ||
| span, | ||
| )) | ||
| let mut node = AstBuilder::sql_expression("for_statement"); | ||
| if let Some(ref label) = inner.label { | ||
| node = node.with_metadata("label".into(), label.clone()); | ||
| } | ||
| node = node.with_metadata("variable".into(), inner.variable.clone()); | ||
| for s in &inner.body { | ||
| node = node.add_child(convert_pl_statement(s, span.as_ref())?); | ||
| } | ||
| Ok(apply_span(node, span)) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how PlStatement::For is destructured/used across the repo, and whether a source/query/range field exists.
rg -nP -C3 'PlStatement::(For|ForEach)\b' crates/
# Locate the ogsql-parser For AST definition in the cargo registry source, if vendored/available.
fd -t f -e rs . ~/.cargo/registry/src 2>/dev/null | xargs -r rg -lP 'struct\s+(PlFor|ForStatement)\b' 2>/dev/null | headRepository: c2j/astgrep
Length of output: 1458
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant adapter code with line numbers.
sed -n '180,220p' crates/astgrep-parser/src/adapter/ogsql/pl.rs | cat -n
# Find the ogsql-parser dependency version used by this repo.
rg -n '^ogsql-parser\b|ogsql-parser\s*=' Cargo.lock crates/**/Cargo.toml
# Try to locate the parser source in the local cargo registry and inspect the For-related structs/enums.
python3 - <<'PY'
import os, glob, re, pathlib
roots = glob.glob(os.path.expanduser('~/.cargo/registry/src/*/ogsql-parser-*'))
print("\n".join(roots[:20]))
PY
# If the source exists, inspect definitions and any `PlStatement::For` construction/fields.
for d in $(python3 - <<'PY'
import os, glob
for p in glob.glob(os.path.expanduser('~/.cargo/registry/src/*/ogsql-parser-*')):
print(p)
PY
); do
echo "=== $d ==="
rg -n -C3 'enum\s+PlStatement\b|struct\s+.*For|PlStatement::For\b|ForEach\b' "$d" || true
doneRepository: c2j/astgrep
Length of output: 2664
🌐 Web query:
c2j ogsql-parser v0.8.31 PlStatement For struct fields ForEach expression
💡 Result:
Based on the provided information, there is no public record or library identified as "c2j ogsql-parser" version 0.8.31 that specifically defines a "PlStatement" for "struct fields" or "ForEach" expressions. The terms you mentioned appear to belong to different domains: 1. "ForEach" expressions: This is a well-documented function in the Appian low-code platform (e.g., a!forEach) [1][2] and a control structure in PL/pgSQL (FOREACH... IN ARRAY) [3]. 2. "PlStatement": In the context of PostgreSQL's PL/pgSQL, internal AST representations often use structures like PLForEachA to represent FOREACH loops [4]. 3. "OGSQL": This name is associated with language models fine-tuned for natural language-to-SQL tasks (e.g., OGSQL-7B) [5][6][7], rather than a parser library or tool with the versioning scheme "v0.8.31". It is possible that "c2j" refers to a proprietary or internal tool, or that the query combines disparate technical terms. If you are working with a specific software project, you may want to verify the library name or check your internal documentation.
Citations:
- 1: https://docs.appian.com/suite/help/26.6/fnc_looping_a_foreach.html
- 2: https://docs.appian.com/suite/help/26.5/fnc_looping_a_foreach.html
- 3: https://www.postgresql.org/docs/current/plpgsql-control-structures.html
- 4: https://github.com/bytebase/omni/blob/8f57f04f5aa2/pg/plpgsql/ast/nodes.go
- 5: https://huggingface.co/OneGate/OGSQL-7B
- 6: https://huggingface.co/OneGate/OGSQL-Mistral-7B
- 7: https://huggingface.co/OneGate/OG-SQL-7B
🏁 Script executed:
#!/bin/bash
set -euo pipefail
repo='c2j/ogsql-parser'
tag='v0.8.31'
echo "== tag refs =="
git ls-remote --tags https://github.com/$repo.git "$tag" "$tag^{}" || true
echo
echo "== repository tree paths containing pl / statement =="
tmpdir="$(mktemp -d)"
git clone --depth 1 --branch "$tag" https://github.com/$repo.git "$tmpdir" >/dev/null 2>&1 || {
echo "clone failed"
exit 0
}
cd "$tmpdir"
rg -n --hidden -S 'enum\s+PlStatement\b|struct\s+.*For|ForEach|ReturnQuery' . || trueRepository: c2j/astgrep
Length of output: 15173
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the PL/pgSQL AST definitions around PlForStmt and PlForEachStmt.
sed -n '100,340p' src/ast/plpgsql.rs | cat -n
# Inspect the parser code that constructs PlStatement::For and PlStatement::ForEach.
sed -n '1680,1765p' src/parser/plpgsql.rs | cat -nRepository: c2j/astgrep
Length of output: 211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the PL/pgSQL AST and parser source in this workspace.
fd -H -t f 'plpgsql.rs' .
fd -H -t f 'pl.rs' crates
# Show the AST definition and parser construction for the For/ForEach statements once found.
for f in $(fd -H -t f 'plpgsql.rs' .); do
echo "=== $f ==="
rg -n -C4 'enum\s+PlStatement\b|struct\s+PlForStmt|struct\s+PlForEachStmt|PlStatement::For\b|PlStatement::ForEach\b' "$f" || true
done
for f in $(fd -H -t f 'pl.rs' crates); do
echo "=== $f ==="
rg -n -C4 'PlStatement::For\b|PlStatement::ForEach\b|with_metadata\("variable"|convert_expr\(' "$f" || true
doneRepository: c2j/astgrep
Length of output: 4499
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search repo docs and source for the PL/pgSQL For statement shape.
rg -n -C4 'PlForStmt|PlForEachStmt|PlStatement::For\b|PlStatement::ForEach\b|FOR .* IN .* LOOP|IN EXECUTE' docs crates src
# Inspect the most relevant docs that describe the AST shape.
sed -n '300,360p' docs/plans/2026-04-05-plpgsql-support.md | cat -n
sed -n '640,660p' docs/plans/2026-04-10-ast-reverse-engineering.md | cat -n
sed -n '120,190p' docs/ogsql-parser-visitor-enhancement-spec.md | cat -nRepository: c2j/astgrep
Length of output: 2829
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the repo docs that define or describe the PL/pgSQL For AST shape.
for f in \
docs/plans/2026-04-05-plpgsql-support.md \
docs/plans/2026-04-10-ast-reverse-engineering.md \
docs/ogsql-parser-visitor-enhancement-spec.md \
docs/ogsql-parser-visitor-enhancement-spec-v2.md
do
if [ -f "$f" ]; then
echo "=== $f ==="
rg -n -C5 'PlForStmt|PlForEachStmt|PlStatement::For\b|PlStatement::ForEach\b|FOR .* IN .* LOOP|IN EXECUTE' "$f" || true
fi
doneRepository: c2j/astgrep
Length of output: 149
🏁 Script executed:
#!/bin/bash
set -euo pipefail
repo='c2j/ogsql-parser'
tag='v0.8.31'
tmpdir="$(mktemp -d)"
git clone --depth 1 --branch "$tag" https://github.com/$repo.git "$tmpdir" >/dev/null 2>&1
cd "$tmpdir"
echo "== ast/plpgsql.rs (PlForStmt / PlStatement) =="
sed -n '100,340p' src/ast/plpgsql.rs | cat -n
echo
echo "== parser/plpgsql.rs (For construction) =="
rg -n -C5 'PlStatement::For\b|PlStatement::ForEach\b' src/parser/plpgsql.rsRepository: c2j/astgrep
Length of output: 9483
FOR needs to include its kind source
PlForStmt.kind carries the loop source (Range, Query, or Cursor), but this arm only records label, variable, and body. Attach the kind payload as a child so nested SELECTs and range expressions stay visible in the tree.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/astgrep-parser/src/adapter/ogsql/pl.rs` around lines 192 - 203, Update
the PlStatement::For conversion to add inner.kind as a child of the
“for_statement” node, preserving its source structure so query and range
expressions remain visible alongside the existing label, variable, and body
children.
| let needs_ogsql = trimmed.contains('@') | ||
| || trimmed.contains(":=") | ||
| || trimmed.matches(';').count() > 1 | ||
| || trimmed.contains("IF ") | ||
| || trimmed.contains("FOR ") | ||
| || trimmed.contains("LOOP") | ||
| || trimmed.contains("WHILE ") | ||
| || trimmed.contains("CASE ") | ||
| || trimmed.contains("CURSOR") | ||
| || trimmed.contains("END IF") | ||
| || trimmed.contains("END LOOP") | ||
| || trimmed.contains("END CASE"); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '180,260p' crates/astgrep-parser/src/pattern_tree.rs
printf '\n---\n'
sed -n '260,340p' crates/astgrep-parser/src/pattern_tree.rsRepository: c2j/astgrep
Length of output: 7075
🏁 Script executed:
rg -n "needs_ogsql|parse_ogsql|to_ascii_uppercase|contains\\(\"IF |contains\\(\"FOR |contains\\(\"LOOP|contains\\(\"CASE |contains\\(\"END IF|contains\\(\"END LOOP|contains\\(\"END CASE|contains\\(\"CURSOR|contains\\(\"@\"|contains\\(\":=\"" crates/astgrep-parser/srcRepository: c2j/astgrep
Length of output: 2238
🏁 Script executed:
rg -n "sql|ogsql|case-insensitive|uppercase" crates/astgrep-parser/src crates/astgrep-parser -g '!target'Repository: c2j/astgrep
Length of output: 50368
🏁 Script executed:
sed -n '1,180p' crates/astgrep-parser/src/pattern_tree.rsRepository: c2j/astgrep
Length of output: 6686
🏁 Script executed:
rg -n 'BEGIN|END|IF |FOR |LOOP|WHILE |CASE |CURSOR|lowercase|mixed case|to_uppercase|to_ascii_uppercase' crates/astgrep-parser/src/adapter/ogsql crates/astgrep-parser/src/pattern_tree.rs crates/astgrep-parser/src/sql.rsRepository: c2j/astgrep
Length of output: 8885
🏁 Script executed:
rg -n "PatternTreeParser|parse_ogsql|PL/pgSQL|ogsql" crates/astgrep-parser/src -g '*test*' -g '*.rs'Repository: c2j/astgrep
Length of output: 29097
🏁 Script executed:
rg -n "fn preprocess_pattern|preprocess_pattern\\(" crates/astgrep-parser/src/pattern_tree.rs
sed -n '840,980p' crates/astgrep-parser/src/pattern_tree.rsRepository: c2j/astgrep
Length of output: 6137
🏁 Script executed:
rg -n '"[a-z].*(if|for|loop|while|case|cursor)|if x|for r|loop|null|end if|end loop|end case' crates/astgrep-parser/src/adapter/ogsql/pl.rs crates/astgrep-parser/src/adapter/ogsql/*.rsRepository: c2j/astgrep
Length of output: 5092
🏁 Script executed:
sed -n '980,1120p' crates/astgrep-parser/src/pattern_tree.rsRepository: c2j/astgrep
Length of output: 5950
🏁 Script executed:
sed -n '320,420p' crates/astgrep-parser/src/adapter/ogsql/pl.rsRepository: c2j/astgrep
Length of output: 3722
🏁 Script executed:
sed -n '923,1010p' crates/astgrep-parser/src/pattern_tree.rsRepository: c2j/astgrep
Length of output: 3887
🏁 Script executed:
rg -n 'BEGIN|END|IF|FOR|LOOP|WHILE|CASE|CURSOR' crates/astgrep-parser/src/adapter/ogsql/pl.rsRepository: c2j/astgrep
Length of output: 1557
Make the ogsql keyword gate case-insensitive
These contains() checks only match uppercase PL/pgSQL keywords, so lowercase or mixed-case patterns can skip parse_ogsql and fall back to tree-sitter. Normalize once, and apply the same change to the retry branch below.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/astgrep-parser/src/pattern_tree.rs` around lines 211 - 222, Update the
needs_ogsql detection in pattern parsing to normalize the trimmed pattern once
for case-insensitive keyword checks, while preserving the symbol and syntax
checks. Apply the same normalized matching logic to the retry branch before
parse_ogsql so lowercase and mixed-case PL/pgSQL keywords take the ogsql path.
Summary
Implements all 4 work items from Issue #41: enhanced PL/SQL & GaussDB SQL procedural language support for the GaussDB dialect (ogsql-parser backend).
P0: Cross-Statement Metavar Unification (~50 lines)
P1: Control Flow Body Recursion (~75 lines)
P2: Clause-Level AST Children (~120 lines)
P3: Cursor Declaration Metadata (~3 lines)
Fix: Pattern Routing for PL/pgSQL Keywords
Regression Tests
Test Results
Files Changed
Summary by CodeRabbit
New Features
SELECT,INSERT,UPDATE, control-flow statements, cursors, joins, grouping, ordering, and filtering.CASEstatements.Bug Fixes
Tests