Skip to content

feat(gaussdb): enhance PL/SQL procedural language support (closes #41) - #42

Merged
c2j merged 1 commit into
mainfrom
feat/issue-41-plsql-enhance
Jul 22, 2026
Merged

c2j merged 1 commit into
mainfrom
feat/issue-41-plsql-enhance

Conversation

@c2j

@c2j c2j commented Jul 21, 2026

Copy link
Copy Markdown
Owner

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)

  • Plain metavars ($VAR without `@attr`) now participate in post-match verification via `_ogsql_bind`
  • AST-structure-based counting for plain metavar consistency
  • Plain metavar bound values included in `bind_metavars_with_backtrack` intersection

P1: Control Flow Body Recursion (~75 lines)

  • IF/CASE/LOOP/WHILE/FOR/FOREACH now recursively convert body statements to child UniversalNodes
  • Enables matching `FOR $VAR IN (...) LOOP $...BODY END LOOP` and similar patterns

P2: Clause-Level AST Children (~120 lines)

  • SELECT: SELECT_LIST, FROM_CLAUSE, GROUP_BY, HAVING_CLAUSE, ORDER_BY children
  • INSERT: VALUES_CLAUSE with VALUES_ROW sub-nodes
  • UPDATE: SET_CLAUSE wrapping assignments
  • Enables `$...` wildcard scoping within individual SQL clauses

P3: Cursor Declaration Metadata (~3 lines)

  • `scrollable` attribute attached to CURSOR declaration nodes

Fix: Pattern Routing for PL/pgSQL Keywords

  • Added IF, FOR, LOOP, WHILE, CASE, CURSOR, END IF, END LOOP, END CASE as ogsql routing triggers
  • Previously these patterns were incorrectly routed to tree-sitter-sequel (no PL/pgSQL support)

Regression Tests

  • 8 rules + 13 test cases in `tests/categories/sql_dialects/gaussdb/cases/regression_issue41/`
  • All 13/13 PASS

Test Results

  • Unit tests: 484 parser + matcher tests, 0 failures
  • Verified end-to-end: `FOR $VAR IN (SELECT $...C FROM $T) LOOP $...BODY END LOOP` matches real DO blocks

Files Changed

  • `pattern_tree.rs` — ogsql routing + plain metavar collection
  • `tree_matcher.rs` — verify_metavar_consistency + bind_metavars_with_backtrack
  • `pl.rs` — control flow body recursion + cursor declaration
  • `dml.rs` — clause-level AST children
  • `expr.rs` — test update for new clause structure
  • 14 new test files

Summary by CodeRabbit

  • New Features

    • Enhanced SQL and PL/pgSQL parsing for SELECT, INSERT, UPDATE, control-flow statements, cursors, joins, grouping, ordering, and filtering.
    • Improved syntax structure and metadata for more precise pattern matching.
    • Expanded support for PL/pgSQL pattern detection, including loops, conditionals, and CASE statements.
  • Bug Fixes

    • Strengthened metavariable consistency checks and backtracking behavior.
  • Tests

    • Added comprehensive GaussDB and openGauss regression coverage for clauses, cursors, and cross-statement table matching.

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)
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

OGSQL 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.

Changes

OGSQL parsing and matching

Layer / File(s) Summary
PL/pgSQL routing and metavariable deduplication
crates/astgrep-parser/src/pattern_tree.rs
Pattern routing and DO-block retry logic recognize additional PL/pgSQL keywords and terminators, while metavariable deduplication distinguishes absent and explicit bind attributes.
Structured OGSQL DML nodes
crates/astgrep-parser/src/adapter/ogsql/dml.rs, crates/astgrep-parser/src/adapter/ogsql/expr.rs
SELECT, INSERT, and UPDATE conversions now create structured clause, target, table, grouping, sorting, values, and assignment nodes; related parser assertions follow the new child layout.
PL/pgSQL control-flow conversion
crates/astgrep-parser/src/adapter/ogsql/pl.rs
IF, CASE, LOOP, WHILE, FOR, FOREACH, and cursor declarations now include converted children and metadata, with tests for DO-block control flow.
Plain metavariable consistency and backtracking
crates/astgrep-matcher/src/tree_matcher.rs
Plain metavariables are validated against repeated trimmed node text values, and backtracking reuses existing plain bindings as candidates.
GaussDB regression rules and cases
tests/categories/sql_dialects/gaussdb/rules/regression_issue41.yaml, tests/categories/sql_dialects/gaussdb/cases/regression_issue41/*
Regression coverage adds SELECT clause, table-unification, and cursor-declaration rules with matching and non-matching SQL cases.

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
Loading

Possibly related PRs

  • c2j/astgrep#30: Updates the same metavariable consistency logic in tree_matcher.rs.
  • c2j/astgrep#33: Modifies the same metavariable backtracking and binding paths.
  • c2j/astgrep#17: Adds the OGSQL DML conversion entry points extended here.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: enhanced GaussDB PL/SQL procedural language support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-41-plsql-enhance

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
crates/astgrep-parser/src/adapter/ogsql/pl.rs (1)

130-168: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

IF/CASE else_stmts are flattened as direct children, indistinguishable from THEN/WHEN bodies.

ELSIF gets an elsif_branch wrapper and CASE whens get case_when wrappers, but else_stmts are appended directly onto the if_statement/case_statement node (Lines 145-147, 164-166). A matcher can't tell THEN/WHEN statements apart from ELSE statements. Consider wrapping ELSE bodies in an else_branch node 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 win

Extract 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 @ and CURSOR). 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 inline needs_ogsql keyword 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 @/CURSOR differences 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 win

Add coverage for the advertised opengauss dialect.

Line 5 declares support for both gaussdb and opengauss, but the supplied fixtures and validation path exercise only --dialect gaussdb. Add equivalent OpenGauss coverage or remove opengauss until 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

📥 Commits

Reviewing files that changed from the base of the PR and between 92a6708 and fb778be.

📒 Files selected for processing (19)
  • crates/astgrep-matcher/src/tree_matcher.rs
  • crates/astgrep-parser/src/adapter/ogsql/dml.rs
  • crates/astgrep-parser/src/adapter/ogsql/expr.rs
  • crates/astgrep-parser/src/adapter/ogsql/pl.rs
  • crates/astgrep-parser/src/pattern_tree.rs
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_group_by.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_group_by_multi.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_having.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_having_agg.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_order_by.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_order_by_desc.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_multi_col.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_no_where.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_single_col.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/clause_select_where.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/cursor_decl.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/unify_table_match.sql
  • tests/categories/sql_dialects/gaussdb/cases/regression_issue41/unify_table_mismatch.sql
  • tests/categories/sql_dialects/gaussdb/rules/regression_issue41.yaml

Comment on lines +2191 to +2195
/// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +2390 to +2397
} 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()]));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines 192 to 203
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))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 | head

Repository: 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
done

Repository: 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:


🏁 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' . || true

Repository: 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 -n

Repository: 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
done

Repository: 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 -n

Repository: 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
done

Repository: 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.rs

Repository: 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.

Comment on lines +211 to +222
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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.rs

Repository: 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/src

Repository: 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.rs

Repository: 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.rs

Repository: 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.rs

Repository: 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/*.rs

Repository: c2j/astgrep

Length of output: 5092


🏁 Script executed:

sed -n '980,1120p' crates/astgrep-parser/src/pattern_tree.rs

Repository: c2j/astgrep

Length of output: 5950


🏁 Script executed:

sed -n '320,420p' crates/astgrep-parser/src/adapter/ogsql/pl.rs

Repository: c2j/astgrep

Length of output: 3722


🏁 Script executed:

sed -n '923,1010p' crates/astgrep-parser/src/pattern_tree.rs

Repository: 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.rs

Repository: 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.

@c2j
c2j merged commit 47f8327 into main Jul 22, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant