Skip to content

Commit

Permalink
#548: feature: query fingerprinting (#692)
Browse files Browse the repository at this point in the history
  • Loading branch information
kakdeykaushik authored Oct 5, 2024
1 parent d33cbf7 commit b52dc11
Show file tree
Hide file tree
Showing 3 changed files with 507 additions and 8 deletions.
8 changes: 0 additions & 8 deletions internal/sql/dsql.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"strconv"
"strings"

hash "github.com/dgryski/go-farm"

"github.com/xwb1989/sqlparser"
)

Expand Down Expand Up @@ -275,9 +273,3 @@ func parseWhere(selectStmt *sqlparser.Select) sqlparser.Expr {
}
return selectStmt.Where.Expr
}

func generateFingerprint(where sqlparser.Expr) string {
// Generate a unique fingerprint for the query
// TODO: Add logic to ensure that logically equivalent WHERE clause expressions generate the same fingerprint.
return fmt.Sprintf("f_%d", hash.Hash64([]byte(sqlparser.String(where))))
}
105 changes: 105 additions & 0 deletions internal/sql/fingerprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package sql

import (
"fmt"
"sort"
"strings"

hash "github.com/dgryski/go-farm"
"github.com/xwb1989/sqlparser"
)

// OR terms containing AND expressions
type expression [][]string

func (expr expression) String() string {
var orTerms []string
for _, andTerm := range expr {
// Sort AND terms within OR
sort.Strings(andTerm)
orTerms = append(orTerms, strings.Join(andTerm, " AND "))
}
// Sort the OR terms
sort.Strings(orTerms)
return strings.Join(orTerms, " OR ")
}

func generateFingerprint(where sqlparser.Expr) string {
expr := parseAstExpression(where)
return fmt.Sprintf("f_%d", hash.Hash64([]byte(expr.String())))
}

func parseAstExpression(expr sqlparser.Expr) expression {
switch expr := expr.(type) {
case *sqlparser.AndExpr:
leftExpr := parseAstExpression(expr.Left)
rightExpr := parseAstExpression(expr.Right)
return combineAnd(leftExpr, rightExpr)
case *sqlparser.OrExpr:
leftExpr := parseAstExpression(expr.Left)
rightExpr := parseAstExpression(expr.Right)
return combineOr(leftExpr, rightExpr)
case *sqlparser.ParenExpr:
return parseAstExpression(expr.Expr)
case *sqlparser.ComparisonExpr:
return expression([][]string{{expr.Operator + sqlparser.String(expr.Left) + sqlparser.String(expr.Right)}})
default:
return expression{}
}
}

func combineAnd(a, b expression) expression {
result := make(expression, 0, len(a)+len(b))
for _, termA := range a {
for _, termB := range b {
combined := make([]string, len(termA), len(termA)+len(termB))
copy(combined, termA)
combined = append(combined, termB...)
uniqueCombined := removeDuplicates(combined)
sort.Strings(uniqueCombined)
result = append(result, uniqueCombined)
}
}
return result
}

func combineOr(a, b expression) expression {
result := make(expression, 0, len(a)+len(b))
uniqueTerms := make(map[string]bool)

// Helper function to add unique terms
addUnique := func(terms []string) {
// Sort the terms for consistent ordering
sort.Strings(terms)
key := strings.Join(terms, ",")
if !uniqueTerms[key] {
result = append(result, terms)
uniqueTerms[key] = true
}
}

// Add unique terms from a
for _, terms := range a {
addUnique(append([]string(nil), terms...))
}

// Add unique terms from b
for _, terms := range b {
addUnique(append([]string(nil), terms...))
}

return result
}

// helper
func removeDuplicates(input []string) []string {
seen := make(map[string]struct{})
var result []string
for _, v := range input {
if _, exists := seen[v]; !exists {
seen[v] = struct{}{}
result = append(result, v)
}
}
return result
}
Loading

0 comments on commit b52dc11

Please sign in to comment.