Skip to content

Commit fadc687

Browse files
feat: Add query analysis and index recommendations (Option 2, fixes #50) (#81)
Implement AnalyzeQuery() function that analyzes CEL expressions and recommends PostgreSQL indexes to optimize query performance. Features: - Detects JSON/JSONB path operations → recommends GIN indexes - Detects array operations and comprehensions → recommends GIN indexes - Detects regex matching → recommends GIN indexes with pg_trgm - Detects comparison operations → recommends B-tree indexes - Returns complete CREATE INDEX statements with explanations Implementation: - New analysis.go with AnalyzeQuery() function - IndexRecommendation struct with Column, IndexType, Expression, Reason - Comprehensive test coverage in analysis_test.go - Working example in examples/index_analysis/ - Updated README.md and CLAUDE.md with usage documentation Testing: - All tests pass (make test) - Code passes linting (make lint) - 72.5% code coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 210e57f commit fadc687

File tree

6 files changed

+1355
-0
lines changed

6 files changed

+1355
-0
lines changed

CLAUDE.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,72 @@ sqlCondition, err := cel2sql.Convert(ast)
210210
// Returns: table.field = 'value' AND table.age > 30
211211
```
212212

213+
### Query Analysis and Index Recommendations
214+
215+
cel2sql can analyze CEL expressions and recommend database indexes to optimize performance.
216+
217+
#### Using AnalyzeQuery
218+
219+
```go
220+
ast, issues := env.Compile(`person.age > 18 && person.metadata.verified == true`)
221+
if issues != nil && issues.Err() != nil {
222+
return issues.Err()
223+
}
224+
225+
sql, recommendations, err := cel2sql.AnalyzeQuery(ast,
226+
cel2sql.WithSchemas(schemas))
227+
if err != nil {
228+
return err
229+
}
230+
231+
// Use the generated SQL
232+
rows, err := db.Query("SELECT * FROM people WHERE " + sql)
233+
234+
// Review and apply index recommendations
235+
for _, rec := range recommendations {
236+
fmt.Printf("Column: %s, Type: %s\n", rec.Column, rec.IndexType)
237+
fmt.Printf("Reason: %s\n", rec.Reason)
238+
fmt.Printf("Execute: %s\n\n", rec.Expression)
239+
}
240+
```
241+
242+
#### Index Recommendation Types
243+
244+
AnalyzeQuery detects patterns and recommends appropriate index types:
245+
246+
- **B-tree indexes**: Comparison operations (`==, >, <, >=, <=`)
247+
- Best for: Equality checks, range queries, sorting
248+
- Example: `person.age > 18` → B-tree on `person.age`
249+
250+
- **GIN indexes**: JSON/JSONB path operations, array operations
251+
- Best for: JSON field access, array membership, containment
252+
- Example: `person.metadata.verified == true` → GIN on `person.metadata`
253+
- Example: `"premium" in person.tags` → GIN on `person.tags`
254+
255+
- **GIN indexes with pg_trgm**: Regex pattern matching
256+
- Best for: Text search, pattern matching, fuzzy matching
257+
- Requires: PostgreSQL pg_trgm extension
258+
- Example: `person.email.matches(r"@example\.com$")` → GIN on `person.email`
259+
260+
#### IndexRecommendation Structure
261+
262+
```go
263+
type IndexRecommendation struct {
264+
Column string // Full column name (e.g., "person.metadata")
265+
IndexType string // "BTREE", "GIN", or "GIST"
266+
Expression string // Complete CREATE INDEX statement
267+
Reason string // Explanation of why this index is recommended
268+
}
269+
```
270+
271+
#### When to Use
272+
273+
- **Development**: Discover which indexes your queries need
274+
- **Performance tuning**: Identify missing indexes causing slow queries
275+
- **Production monitoring**: Analyze user-generated filter expressions
276+
277+
See `examples/index_analysis/` for a complete working example.
278+
213279
### Logging and Observability
214280

215281
cel2sql supports structured logging using Go's standard `log/slog` package (Go 1.21+).

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,79 @@ sql, err := cel2sql.Convert(ast,
119119
- `WithLogger(*slog.Logger)` - Enable structured logging
120120
- `WithMaxDepth(int)` - Set custom recursion depth limit (default: 100)
121121

122+
## Query Analysis and Index Recommendations
123+
124+
cel2sql can analyze your CEL queries and recommend database indexes to optimize performance. The `AnalyzeQuery()` function returns both the converted SQL and actionable index recommendations.
125+
126+
### How It Works
127+
128+
`AnalyzeQuery()` examines your CEL expression and detects patterns that would benefit from specific PostgreSQL index types:
129+
130+
- **JSON/JSONB path operations** (`->>, ?`) → GIN indexes
131+
- **Array operations** (comprehensions, `IN` clauses) → GIN indexes
132+
- **Regex matching** (`matches()`) → GIN indexes with `pg_trgm` extension
133+
- **Comparison operations** (`==, >, <, >=, <=`) → B-tree indexes
134+
135+
### Usage
136+
137+
```go
138+
sql, recommendations, err := cel2sql.AnalyzeQuery(ast,
139+
cel2sql.WithSchemas(schemas))
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
144+
// Use the generated SQL
145+
rows, err := db.Query("SELECT * FROM users WHERE " + sql)
146+
147+
// Review and apply index recommendations
148+
for _, rec := range recommendations {
149+
fmt.Printf("Column: %s\n", rec.Column)
150+
fmt.Printf("Type: %s\n", rec.IndexType)
151+
fmt.Printf("Reason: %s\n", rec.Reason)
152+
fmt.Printf("Execute: %s\n\n", rec.Expression)
153+
154+
// Apply the recommendation
155+
// _, err := db.Exec(rec.Expression)
156+
}
157+
```
158+
159+
### Example
160+
161+
```go
162+
// Query with multiple index-worthy patterns
163+
celExpr := `person.age > 18 &&
164+
person.email.matches(r"@example\.com$") &&
165+
person.metadata.verified == true`
166+
167+
ast, _ := env.Compile(celExpr)
168+
sql, recs, _ := cel2sql.AnalyzeQuery(ast, cel2sql.WithSchemas(schemas))
169+
170+
// Generated SQL:
171+
// person.age > 18 AND person.email ~ '@example\.com$'
172+
// AND person.metadata->>'verified' = 'true'
173+
174+
// Recommendations:
175+
// 1. CREATE INDEX idx_person_age_btree ON table_name (person.age);
176+
// Reason: Comparison operations benefit from B-tree for range queries
177+
//
178+
// 2. CREATE INDEX idx_person_email_gin_trgm ON table_name
179+
// USING GIN (person.email gin_trgm_ops);
180+
// Reason: Regex matching benefits from GIN index with pg_trgm
181+
//
182+
// 3. CREATE INDEX idx_person_metadata_gin ON table_name
183+
// USING GIN (person.metadata);
184+
// Reason: JSON path operations benefit from GIN index
185+
```
186+
187+
### When to Use
188+
189+
- **Development**: Discover which indexes your queries need
190+
- **Performance tuning**: Identify missing indexes causing slow queries
191+
- **Production monitoring**: Analyze user-generated filter expressions
192+
193+
See `examples/index_analysis/` for a complete working example.
194+
122195
## Parameterized Queries
123196

124197
cel2sql supports **parameterized queries** (prepared statements) for improved performance, security, and monitoring.

0 commit comments

Comments
 (0)