elastic / elastic/beats

[bug-hunter] Metricbeat SQL cursor parser counts :cursor inside PostgreSQL dollar-quoted strings

Open
#50,153 1 comment 0 reactions 0 assignees View on GitHub
needs_team
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.