Documentation
¶
Overview ¶
Package cel2squirrel provides a converter for transforming Common Expression Language (CEL) expressions into SQL WHERE clauses using the Squirrel SQL builder.
Example ¶
Example demonstrates basic usage of the CEL to SQL converter
package main
import (
"fmt"
"log"
"github.com/Masterminds/squirrel"
"github.com/google/cel-go/cel"
"zntr.io/cel2squirrel"
)
func main() {
// Define field declarations (CEL variables and their types)
config := cel2squirrel.Config{
FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
"status": {Type: cel.StringType, Column: "status"},
"age": {Type: cel.IntType, Column: "age"},
},
}
// Create converter
converter, err := cel2squirrel.NewConverter(config)
if err != nil {
log.Fatal(err)
}
// Convert CEL expression to SQL
celExpr := `status == "published" && age >= 18`
result, err := converter.Convert(celExpr)
if err != nil {
log.Fatal(err)
}
// Build complete query with Squirrel
query := squirrel.Select("*").
From("prompts").
Where(result.Where)
sql, args, err := query.ToSql()
if err != nil {
log.Fatal(err)
}
fmt.Println(sql)
fmt.Printf("Args: %v\n", args)
}
Output: SELECT * FROM prompts WHERE (status = ? AND age >= ?) Args: [published 18]
Example (ComplexExpression) ¶
Example_complexExpression demonstrates nested boolean logic
package main
import (
"fmt"
"log"
"github.com/Masterminds/squirrel"
"github.com/google/cel-go/cel"
"zntr.io/cel2squirrel"
)
func main() {
config := cel2squirrel.Config{
FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
"status": {Type: cel.StringType, Column: "status"},
"age": {Type: cel.IntType, Column: "age"},
"rating": {Type: cel.DoubleType, Column: "rating"},
},
}
converter, err := cel2squirrel.NewConverter(config)
if err != nil {
log.Fatal(err)
}
celExpr := `(status == "published" || status == "featured") && age >= 18 && rating > 4.0`
result, err := converter.Convert(celExpr)
if err != nil {
log.Fatal(err)
}
query := squirrel.Select("id", "label", "rating").
From("prompts").
Where(result.Where).
OrderBy("rating DESC").
Limit(10)
sql, args, _ := query.ToSql()
fmt.Println(sql)
fmt.Printf("Args: %v\n", args)
}
Output: SELECT id, label, rating FROM prompts WHERE (((status = ? OR status = ?) AND age >= ?) AND rating > ?) ORDER BY rating DESC LIMIT 10 Args: [published featured 18 4]
Example (FieldMappings) ¶
Example_fieldMappings demonstrates mapping CEL field names to SQL column names
package main
import (
"fmt"
"log"
"github.com/google/cel-go/cel"
"zntr.io/cel2squirrel"
)
func main() {
config := cel2squirrel.Config{
FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
"isDraft": {Type: cel.BoolType, Column: "is_draft"},
"ownerId": {Type: cel.StringType, Column: "owner_id"},
},
}
converter, err := cel2squirrel.NewConverter(config)
if err != nil {
log.Fatal(err)
}
// Column mappings are now part of the config
celExpr := `isDraft == false && ownerId == "user123"`
result, err := converter.Convert(celExpr)
if err != nil {
log.Fatal(err)
}
sql, args, _ := result.Where.ToSql()
fmt.Println(sql)
fmt.Printf("Args: %v\n", args)
}
Output: (is_draft = ? AND owner_id = ?) Args: [false user123]
Example (PostgreSQL) ¶
Example_postgreSQL demonstrates using PostgreSQL-style placeholders
package main
import (
"fmt"
"log"
"github.com/Masterminds/squirrel"
"github.com/google/cel-go/cel"
"zntr.io/cel2squirrel"
)
func main() {
config := cel2squirrel.Config{
FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
"status": {Type: cel.StringType, Column: "status"},
"age": {Type: cel.IntType, Column: "age"},
},
}
converter, err := cel2squirrel.NewConverter(config)
if err != nil {
log.Fatal(err)
}
celExpr := `status == "published" && age >= 18`
result, err := converter.Convert(celExpr)
if err != nil {
log.Fatal(err)
}
// Use PostgreSQL placeholder format
query := squirrel.Select("*").
From("prompts").
Where(result.Where).
PlaceholderFormat(squirrel.Dollar)
sql, args, _ := query.ToSql()
fmt.Println(sql)
fmt.Printf("Args: %v\n", args)
}
Output: SELECT * FROM prompts WHERE (status = $1 AND age >= $2) Args: [published 18]
Example (StringOperations) ¶
Example_stringOperations demonstrates CEL string methods
package main
import (
"fmt"
"log"
"github.com/google/cel-go/cel"
"zntr.io/cel2squirrel"
)
func main() {
config := cel2squirrel.Config{
FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
"label": {Type: cel.StringType, Column: "label"},
},
}
converter, err := cel2squirrel.NewConverter(config)
if err != nil {
log.Fatal(err)
}
// Contains
result, err := converter.Convert(`label.contains("gpt")`)
if err != nil {
log.Fatal(err)
}
sql, args, _ := result.Where.ToSql()
fmt.Println("Contains:", sql)
fmt.Printf("Args: %v\n", args)
// Starts with
result, err = converter.Convert(`label.startsWith("prod-")`)
if err != nil {
log.Fatal(err)
}
sql, args, _ = result.Where.ToSql()
fmt.Println("Starts with:", sql)
fmt.Printf("Args: %v\n", args)
// Ends with
result, err = converter.Convert(`label.endsWith("-v2")`)
if err != nil {
log.Fatal(err)
}
sql, args, _ = result.Where.ToSql()
fmt.Println("Ends with:", sql)
fmt.Printf("Args: %v\n", args)
}
Output: Contains: label LIKE ? Args: [%gpt%] Starts with: label LIKE ? Args: [prod-%] Ends with: label LIKE ? Args: [%-v2]
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func QuoteIdentifier ¶
QuoteIdentifier quotes a SQL identifier to prevent SQL injection.
Types ¶
type ColumnMapping ¶
type ColumnMapping struct {
// Type is the type of the CEL field.
Type *cel.Type
// Column is the name of the SQL column.
Column string
}
ColumnMapping is a mapping of a CEL field name to a SQL column name.
type Config ¶
type Config struct {
// FieldDeclarations maps CEL variable names to their types and SQL columns.
// Example: map[string]ColumnMapping{
// "age": {Type: cel.IntType, Column: "user_age"},
// "name": {Type: cel.StringType, Column: "user_name"},
// "tags": {Type: cel.ListType(cel.StringType), Column: "user_tags"},
// }
FieldDeclarations map[string]ColumnMapping
// Security limits to prevent DoS attacks
// MaxExpressionLength is the maximum allowed length of a CEL expression in characters.
// Default: 10000. Set to 0 to apply default.
MaxExpressionLength int
// MaxExpressionDepth is the maximum nesting depth of boolean operators.
// Default: 50. Set to 0 to apply default.
MaxExpressionDepth int
// MaxInClauseSize is the maximum number of values allowed in an IN clause.
// Default: 1000. Set to 0 to apply default.
MaxInClauseSize int
// Authorization settings for field-level access control
// PublicFields is a list of field names that any user can filter by.
// If empty, authorization checks are disabled.
PublicFields []string
// FieldACL maps field names to lists of roles that can access them.
// Only checked if PublicFields is not empty.
FieldACL map[string][]string
}
Config contains configuration for the CEL to SQL converter.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with secure default values.
type ConversionError ¶
type ConversionError struct {
// PublicMessage is a sanitized error message safe to return to end users.
PublicMessage string
// InternalError contains the detailed error for internal logging.
InternalError error
// ErrorCode is a machine-readable error code.
ErrorCode string
}
ConversionError represents an error that occurred during CEL to SQL conversion. It provides both a user-safe public message and detailed internal error for logging.
func (*ConversionError) Error ¶
func (e *ConversionError) Error() string
Error implements the error interface, returning the public message.
func (*ConversionError) Unwrap ¶
func (e *ConversionError) Unwrap() error
Unwrap returns the internal error for error chain unwrapping.
type ConvertResult ¶
type ConvertResult struct {
// Where is the Squirrel Sqlizer that can be used in WHERE clauses
Where squirrel.Sqlizer
// Args contains any arguments that need to be bound to the query
Args []interface{}
}
ConvertResult contains the result of converting a CEL expression to SQL.
type Converter ¶
type Converter struct {
// contains filtered or unexported fields
}
Converter converts CEL expressions to Squirrel SQL builder objects.
func NewConverter ¶
NewConverter creates a new CEL to SQL converter with the given configuration.
func (*Converter) Convert ¶
func (c *Converter) Convert(celExpr string) (*ConvertResult, error)
Convert parses a CEL expression and converts it to a Squirrel SQL builder object. It validates that the expression is boolean and returns a Sqlizer that can be used in WHERE clauses. Column mappings are automatically applied based on the converter's configuration.
func (*Converter) ConvertWithAuth ¶
func (c *Converter) ConvertWithAuth(celExpr string, userRoles []string) (*ConvertResult, error)
ConvertWithAuth converts a CEL expression to SQL with field-level authorization. It checks that the user (identified by their roles) is authorized to filter by all fields referenced in the expression. If authorization is not configured (PublicFields is empty), this behaves the same as Convert().
type SecurityLogger ¶
type SecurityLogger interface {
// LogConversionAttempt logs an attempt to convert a CEL expression to SQL.
LogConversionAttempt(expr string, success bool, err error, duration time.Duration)
// LogComplexExpression logs when an expression is unusually complex.
LogComplexExpression(expr string, depth int, length int)
LogUnauthorizedField(expr string, field string, userRoles []string)
// LogUnsupportedOperation logs when an unsupported CEL function is used.
LogUnsupportedOperation(expr string, operation string)
}
SecurityLogger is an interface for logging security-relevant events. Implementations should log these events to a security audit log for monitoring.