[bug-hunter] Metricbeat SQL cursor parser counts :cursor inside PostgreSQL dollar-quoted strings
- Dominant language
- Go
- Stars
- 12.7k
- Forks
- 5k
- Avg merge
- 2d 15m
- Merged PRs (30d)
- 385
Description
## Impact
Metricbeat SQL `query` metricset with `cursor.enabled: true` rejects valid PostgreSQL/Cockroach queries when `:cursor` appears inside a dollar-quoted string literal/function body. This blocks incremental fetching startup and can also rewrite literal SQL text unexpectedly.
## Reproduction Steps
1. Create and run this minimal reproducer:
```go
package main
import (
"fmt"
"github.com/elastic/beats/v7/x-pack/metricbeat/module/sql/query/cursor"
)
func main() {
query := "SELECT $$literal :cursor$$ AS note, id FROM t WHERE id > :cursor ORDER BY id"
fmt.Printf("query: %s\n", query)
fmt.Printf("count=%d\n", cursor.CountPlaceholders(query))
fmt.Printf("translated=%s\n", cursor.TranslateQuery(query, "postgres"))
if err := cursor.ValidateQueryHasCursor(query); err != nil {
fmt.Printf("validate_error=%v\n", err)
} else {
fmt.Println("validate_error=")
}
}
```
2. Run:
```bash
go run /tmp/gh-aw/agent/repro_cursor.go
```
## Expected vs Actual
**Expected:**
- `CountPlaceholders` should return `1` (only `WHERE id > :cursor` is executable placeholder).
- `TranslateQuery(..., "postgres")` should produce:
`SELECT $$literal :cursor$$ AS note, id FROM t WHERE id > $1 ORDER BY id`
- `ValidateQueryHasCursor` should succeed.
**Actual:**
- `count=2`
- `translated=SELECT $$literal $1$$ AS note, id FROM t WHERE id > $1 ORDER BY id`
- `validate_error=query must contain exactly one :cursor placeholder, found 2`
Observed output:
```text
query: SELECT $$literal :cursor$$ AS note, id FROM t WHERE id > :cursor ORDER BY id
count=2
translated=SELECT $$literal $1$$ AS note, id FROM t WHERE id > $1 ORDER BY id
validate_error=query must contain exactly one :cursor placeholder, found 2
```
## Failing Test
```go
func TestPostgresDollarQuotedLiteralDoesNotCountCursor(t *testing.T) {
query := "SELECT $$literal :cursor$$ AS note, id FROM t WHERE id > :cursor ORDER BY id"
assert.Equal(t, 1, CountPlaceholders(query), "only executable placeholder should be counted")
assert.Equal(t,
"SELECT $$literal :cursor$$ AS note, id FROM t WHERE id > $1 ORDER BY id",
TranslateQuery(query, "postgres"),
"cursor inside dollar-quoted string must not be translated",
)
assert.NoError(t, ValidateQueryHasCursor(query), "valid query should pass cursor validation")
}
```
## Evidence
- `x-pack/metricbeat/module/sql/query/cursor/placeholder.go:120-136` scanner handles `'`, `"`, `` ` ``, `--`, `/* */`, but has no PostgreSQL dollar-quoted-string state.
- `x-pack/metricbeat/module/sql/query/cursor/placeholder.go:25-27` rejects queries when count is >1.
- `x-pack/metricbeat/module/sql/query/cursor/placeholder.go:65-67` explicitly supports `postgres`, `postgresql`, `cockroachdb`, `cockroach`, so this affects supported drivers.
- Existing tests cover quoted strings/comments/identifiers but not dollar-quoted strings: `x-pack/metricbeat/module/sql/query/cursor/placeholder_test.go:61-94`, `223-241`.
---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Bug Hunter](https://github.com/elastic/beats/actions/runs/24507548737)
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Apr 23, 2026, 11:44 AM UTC
Contributor guide
Research direction
Start with x-pack/metricbeat/module/sql/query/cursor/placeholder.go:120-136, where the scanner handles quoted strings and comments, then read the related counting, translation, and validation logic. Run the reproducer and extend x-pack/metricbeat/module/sql/query/cursor/placeholder_test.go near the existing quoted-string tests. Done means dollar-quoted PostgreSQL and CockroachDB text is ignored while the executable :cursor is counted, translated, and validated correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100