Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions pkg/fingerprint/fingerprint_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2026 GoSQLX Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fingerprint_test

import (
"testing"

"github.com/ajitpratap0/GoSQLX/pkg/fingerprint"
)

const benchJoinSQL = "SELECT c.id, c.name, SUM(o.total) AS spend FROM customers c " +
"JOIN orders o ON o.customer_id = c.id LEFT JOIN regions g ON g.code = c.region " +
"WHERE c.active = 1 AND o.created_at > '2024-01-01' GROUP BY c.id, c.name " +
"ORDER BY spend DESC, c.name LIMIT 50"

func BenchmarkFingerprint(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := fingerprint.Fingerprint(benchJoinSQL); err != nil {
b.Fatal(err)
}
}
}

func BenchmarkNormalize(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := fingerprint.Normalize(benchJoinSQL); err != nil {
b.Fatal(err)
}
}
}
7 changes: 7 additions & 0 deletions pkg/sql/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ import (
// The Node interface enables the visitor pattern for AST traversal. Use the
// Walk() and Inspect() functions from visitor.go to traverse the tree.
//
// Children stored by value in a parent (TableReference, JoinClause,
// OrderByExpression, WindowSpec, WhenClause, UpdateExpression, ColumnDef and
// its constraints, SetClause, ...) are returned by Children() as pointers into
// the parent's storage rather than as fresh copies. Mutations through them
// change the AST, and they share the AST's lifetime: do not retain them after
// ast.ReleaseAST, just like every other child pointer.
//
// Example - Checking node type:
//
// switch node := astNode.(type) {
Expand Down
72 changes: 46 additions & 26 deletions pkg/sql/ast/ast_clauses.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,22 @@ type JoinClause struct {

func (j *JoinClause) expressionNode() {}
func (j JoinClause) TokenLiteral() string { return j.Type + " JOIN" }
func (j JoinClause) Children() []Node {
children := []Node{&j.Left, &j.Right}

// Children returns Left, Right and, when present, Condition.
//
// The value receiver is kept for API compatibility. Called on a value it
// returns addresses into the receiver copy (one heap copy per call), while
// Walk and Inspect use childrenInPlace and visit the stored fields in place
// (#557).
func (j JoinClause) Children() []Node { return j.childrenInPlace() }

// childrenInPlace is Children for a JoinClause reached through a pointer:
// Left and Right are returned as pointers into *j itself.
func (j *JoinClause) childrenInPlace() []Node {
if j.Condition != nil {
children = append(children, j.Condition)
return []Node{&j.Left, &j.Right, j.Condition}
}
return children
return []Node{&j.Left, &j.Right}
}

// TableReference represents a table reference in a FROM clause.
Expand Down Expand Up @@ -246,11 +256,16 @@ type WindowSpec struct {
func (w *WindowSpec) statementNode() {}
func (w WindowSpec) TokenLiteral() string { return "WINDOW" }
func (w WindowSpec) Children() []Node {
children := make([]Node, 0)
children = append(children, nodifyExpressions(w.PartitionBy)...)
for _, orderBy := range w.OrderBy {
orderBy := orderBy // G601: Create local copy to avoid memory aliasing
children = append(children, &orderBy)
n := len(w.PartitionBy) + len(w.OrderBy)
if w.FrameClause != nil {
n++
}
children := make([]Node, 0, n)
for _, e := range w.PartitionBy {
children = append(children, e)
}
for i := range w.OrderBy {
children = append(children, &w.OrderBy[i])
}
if w.FrameClause != nil {
children = append(children, w.FrameClause)
Expand All @@ -267,13 +282,22 @@ type WindowFrame struct {

func (w *WindowFrame) statementNode() {}
func (w WindowFrame) TokenLiteral() string { return w.Type }
func (w WindowFrame) Children() []Node {
// Start is a value type, always include it to support visitor traversal.
children := []Node{&w.Start}

// Children returns Start and, when present, End.
//
// The value receiver is kept for API compatibility. Called on a value it
// returns addresses into the receiver copy (one heap copy per call), while
// Walk and Inspect use childrenInPlace and visit the stored fields in place
// (#557).
func (w WindowFrame) Children() []Node { return w.childrenInPlace() }

// childrenInPlace is Children for a WindowFrame reached through a pointer:
// Start is a value type, always included, and returned as a pointer into *w.
func (w *WindowFrame) childrenInPlace() []Node {
if w.End != nil {
children = append(children, w.End)
return []Node{&w.Start, w.End}
}
return children
return []Node{&w.Start}
}

// WindowFrameBound represents window frame bound
Expand Down Expand Up @@ -377,9 +401,8 @@ func (o OnConflict) TokenLiteral() string { return "ON CONFLICT" }
func (o OnConflict) Children() []Node {
children := nodifyExpressions(o.Target)
if o.Action.DoUpdate != nil {
for _, update := range o.Action.DoUpdate {
update := update // G601: Create local copy to avoid memory aliasing
children = append(children, &update)
for i := range o.Action.DoUpdate {
children = append(children, &o.Action.DoUpdate[i])
}
}
if o.Action.Where != nil {
Expand All @@ -404,9 +427,8 @@ func (u *UpsertClause) expressionNode() {}
func (u UpsertClause) TokenLiteral() string { return "ON DUPLICATE KEY UPDATE" }
func (u UpsertClause) Children() []Node {
children := make([]Node, len(u.Updates))
for i, update := range u.Updates {
update := update // G601: Create local copy to avoid memory aliasing
children[i] = &update
for i := range u.Updates {
children[i] = &u.Updates[i]
}
return children
}
Expand All @@ -422,9 +444,8 @@ func (c *ColumnDef) expressionNode() {}
func (c ColumnDef) TokenLiteral() string { return c.Name }
func (c ColumnDef) Children() []Node {
children := make([]Node, len(c.Constraints))
for i, constraint := range c.Constraints {
constraint := constraint // G601: Create local copy to avoid memory aliasing
children[i] = &constraint
for i := range c.Constraints {
children[i] = &c.Constraints[i]
}
return children
}
Expand Down Expand Up @@ -721,9 +742,8 @@ func (m MatchRecognizeClause) TokenLiteral() string { return "MATCH_RECOGNIZE" }
func (m MatchRecognizeClause) Children() []Node {
var nodes []Node
nodes = append(nodes, nodifyExpressions(m.PartitionBy)...)
for _, ob := range m.OrderBy {
ob := ob
nodes = append(nodes, &ob)
for i := range m.OrderBy {
nodes = append(nodes, &m.OrderBy[i])
}
for _, md := range m.Measures {
if md.Expr != nil {
Expand Down
40 changes: 27 additions & 13 deletions pkg/sql/ast/ast_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,23 +153,31 @@ type FunctionCall struct {
func (f *FunctionCall) expressionNode() {}
func (f FunctionCall) TokenLiteral() string { return f.Name }
func (f FunctionCall) Children() []Node {
children := nodifyExpressions(f.Arguments)
if len(f.Parameters) > 0 {
children = append(children, nodifyExpressions(f.Parameters)...)
n := len(f.Arguments) + len(f.Parameters) + len(f.OrderBy) + len(f.WithinGroup)
if f.Over != nil {
n++
}
if f.Filter != nil {
n++
}
children := make([]Node, 0, n)
for _, e := range f.Arguments {
children = append(children, e)
}
for _, e := range f.Parameters {
children = append(children, e)
}
if f.Over != nil {
children = append(children, f.Over)
}
if f.Filter != nil {
children = append(children, f.Filter)
}
for _, orderBy := range f.OrderBy {
orderBy := orderBy // G601: Create local copy to avoid memory aliasing
children = append(children, &orderBy)
for i := range f.OrderBy {
children = append(children, &f.OrderBy[i])
}
for _, orderBy := range f.WithinGroup {
orderBy := orderBy // G601: Create local copy to avoid memory aliasing
children = append(children, &orderBy)
for i := range f.WithinGroup {
children = append(children, &f.WithinGroup[i])
}
return children
}
Expand All @@ -185,13 +193,19 @@ type CaseExpression struct {
func (c *CaseExpression) expressionNode() {}
func (c CaseExpression) TokenLiteral() string { return "CASE" }
func (c CaseExpression) Children() []Node {
children := make([]Node, 0)
n := len(c.WhenClauses)
if c.Value != nil {
n++
}
if c.ElseClause != nil {
n++
}
children := make([]Node, 0, n)
if c.Value != nil {
children = append(children, c.Value)
}
for _, when := range c.WhenClauses {
when := when // G601: Create local copy to avoid memory aliasing
children = append(children, &when)
for i := range c.WhenClauses {
children = append(children, &c.WhenClauses[i])
}
if c.ElseClause != nil {
children = append(children, c.ElseClause)
Expand Down
Loading
Loading