transform

package
v1.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 13, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package transform provides composable SQL query rewriting via AST manipulation.

This is GoSQLX's key differentiator — enabling safe, programmatic SQL modification without string concatenation. All transforms operate on AST nodes from pkg/sql/ast and preserve structural validity, meaning a roundtrip (parse -> transform -> format) always produces well-formed SQL. Transforms are defined by the Rule interface and applied individually or composed using Apply.

Available Transforms

WHERE clause: AddWhere, AddWhereFromSQL, ReplaceWhere, RemoveWhere Columns: AddColumn, RemoveColumn JOINs: AddJoin, AddJoinFromSQL ORDER BY: AddOrderBy LIMIT/OFFSET: SetLimit, SetOffset (for pagination) Tables: RenameTable, AddTableAlias

WHERE Clause Transforms

// Add a filter condition using an AST node (safe for untrusted column values)
rule := transform.AddWhere(&ast.BinaryExpression{
    Left:     &ast.Identifier{Name: "status"},
    Operator: "=",
    Right:    &ast.LiteralValue{Value: "active"},
})
transform.Apply(stmt, rule)

// Add a filter from a trusted SQL string
rule := transform.AddWhereFromSQL("status = 'active'")
transform.Apply(stmt, rule)

Column Transforms

// Add a column to SELECT
rule := transform.AddColumn(&ast.Identifier{Name: "email"})
transform.Apply(stmt, rule)

JOIN Transforms

// Add a JOIN from SQL
rule := transform.AddJoinFromSQL("LEFT JOIN orders ON orders.user_id = users.id")
transform.Apply(stmt, rule)

Pagination

// Set LIMIT and OFFSET for pagination
transform.Apply(stmt,
    transform.SetLimit(20),
    transform.SetOffset(40),
)

Security

WARNING: Functions that accept raw SQL strings (AddWhereFromSQL, AddJoinFromSQL) must not receive untrusted user input. Passing unsanitized input could produce unintended query modifications. Use parameterized queries or construct AST nodes directly (AddWhere, AddJoin) for untrusted input.

Composability

Multiple transforms can be chained in a single Apply call:

transform.Apply(stmt,
    transform.AddWhereFromSQL("active = true"),
    transform.SetLimit(10),
    transform.AddOrderBy("created_at", true),
)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Apply

func Apply(stmt ast.Statement, rules ...Rule) error

Apply executes one or more rules against an AST statement in the order they are provided. If any rule returns a non-nil error the function stops immediately and returns that error without applying subsequent rules.

This is the primary entry point for composing transforms:

err := transform.Apply(stmt,
    transform.AddWhereFromSQL("active = true"),
    transform.SetLimit(100),
    transform.AddOrderBy("created_at", true),
)

func FormatSQL

func FormatSQL(stmt ast.Statement) string

FormatSQL converts an AST statement back into a compact SQL string using the GoSQLX formatter. It is the inverse of ParseSQL and completes the parse-transform-format round-trip.

The output uses compact style with minimal whitespace. Use this after applying transforms to obtain the final SQL to execute or log.

Example:

sql := transform.FormatSQL(stmt)
// "SELECT id, name FROM users WHERE active = true LIMIT 10"

func ParseSQL

func ParseSQL(sql string) (*ast.AST, error)

ParseSQL parses a SQL string into a full AST containing all statements. This is a convenience wrapper around the tokenizer and parser pipeline that handles resource pooling automatically.

Use this function when you need an AST for subsequent Apply calls:

tree, err := transform.ParseSQL("SELECT id, name FROM users WHERE active = true")
if err != nil {
    log.Fatal(err)
}
stmt := tree.Statements[0]
transform.Apply(stmt, transform.SetLimit(10))
fmt.Println(transform.FormatSQL(stmt))

Returns a *ast.AST containing all parsed statements, or an error if tokenization or parsing fails.

Types

type ErrUnsupportedStatement

type ErrUnsupportedStatement struct {
	Transform string
	Got       string
}

ErrUnsupportedStatement is returned when a transform rule is applied to a statement type it does not support. For example, AddColumn only supports SelectStatement; applying it to an InsertStatement will produce this error.

Fields:

  • Transform: Name of the transform function that produced the error (e.g., "AddColumn")
  • Got: Human-readable name of the statement type that was rejected (e.g., "INSERT")

func (*ErrUnsupportedStatement) Error

func (e *ErrUnsupportedStatement) Error() string

type Rule

type Rule interface {
	Apply(stmt ast.Statement) error
}

Rule represents an AST rewrite rule that can be applied to a single SQL statement. Rules modify the AST in-place and return an error if the transform cannot be applied (e.g., applying a SELECT-only rule to an INSERT statement).

Implement this interface to create custom transform rules:

type MyRule struct{}

func (r MyRule) Apply(stmt ast.Statement) error {
    sel, ok := stmt.(*ast.SelectStatement)
    if !ok {
        return nil // skip non-SELECT statements
    }
    // modify sel in-place
    return nil
}

Built-in rules are created by the constructor functions in this package (AddWhere, AddColumn, AddJoin, SetLimit, etc.). Use Apply (the package-level function) to chain multiple rules together.

func AddColumn

func AddColumn(expr ast.Expression) Rule

AddColumn returns a Rule that appends a column expression to the SELECT list of a SELECT statement. The expression may be any valid AST expression node such as *ast.Identifier, *ast.AliasedExpression, or *ast.FunctionCall.

Parameters:

  • expr: The column expression to append to the SELECT list

Returns ErrUnsupportedStatement for non-SELECT statements.

func AddJoin

func AddJoin(joinType string, table string, condition ast.Expression) Rule

AddJoin returns a Rule that appends a JOIN clause to the SELECT statement. The join type is validated against the set of supported types; an error is returned for any unrecognised value.

Parameters:

  • joinType: One of INNER, LEFT, RIGHT, FULL, CROSS, or NATURAL (case-insensitive)
  • table: Name of the table to join
  • condition: AST expression for the ON condition (may be nil for CROSS/NATURAL joins)

Returns an error for unrecognised join types or ErrUnsupportedStatement for non-SELECT statements.

func AddJoinFromSQL

func AddJoinFromSQL(sql string) Rule

AddJoinFromSQL returns a Rule that parses a JOIN clause from SQL and adds it to a SELECT statement. The sql parameter should be a complete JOIN clause, e.g. "LEFT JOIN orders ON orders.user_id = users.id".

WARNING: sql parameter must not contain untrusted user input. This function parses raw SQL — passing unsanitized input could produce unintended query modifications. Use parameterized queries or construct AST nodes directly for untrusted input.

func AddOrderBy

func AddOrderBy(column string, desc bool) Rule

AddOrderBy returns a Rule that appends an ORDER BY expression to a SELECT statement. Multiple calls append additional sort keys in the order they are applied.

Parameters:

  • column: Name of the column (or expression alias) to sort by
  • desc: When true the sort direction is DESC; when false it is ASC

Returns ErrUnsupportedStatement for non-SELECT statements.

func AddSelectStar

func AddSelectStar() Rule

AddSelectStar returns a Rule that appends a wildcard * column to the SELECT list of a SELECT statement. This is a convenience wrapper around AddColumn with an *ast.Identifier{Name: "*"} argument.

Returns ErrUnsupportedStatement for non-SELECT statements.

func AddTableAlias

func AddTableAlias(tableName, alias string) Rule

AddTableAlias returns a Rule that assigns an alias to a specific table in a SELECT, UPDATE, or DELETE statement. For SELECT statements the alias is applied to the matching entry in the FROM clause; for UPDATE and DELETE it sets the statement-level alias field.

Parameters:

  • tableName: The table to alias (case-insensitive match)
  • alias: The alias to assign

Returns ErrUnsupportedStatement for INSERT or DDL statements.

func AddWhere

func AddWhere(condition ast.Expression) Rule

AddWhere returns a Rule that appends a condition to the WHERE clause of a SELECT, UPDATE, or DELETE statement. If the statement already has a WHERE clause, the new condition is combined with AND. If there is no WHERE clause, the condition becomes the sole WHERE predicate.

Use this when you have a pre-built AST expression. For raw SQL strings use AddWhereFromSQL instead.

Parameters:

  • condition: An AST expression node representing the filter predicate

Returns ErrUnsupportedStatement for INSERT or DDL statements.

func AddWhereFromSQL

func AddWhereFromSQL(sql string) Rule

AddWhereFromSQL returns a Rule that parses a SQL condition string and adds it as an AND condition to the existing WHERE clause.

WARNING: sql parameter must not contain untrusted user input. This function parses raw SQL — passing unsanitized input could produce unintended query modifications. Use parameterized queries or construct AST nodes directly for untrusted input.

func QualifyColumns

func QualifyColumns(tableName string) Rule

QualifyColumns returns a Rule that prefixes every unqualified column reference in a SELECT statement's column list and WHERE clause with tableName. Only identifiers that have no existing table qualifier and whose name is not the wildcard "*" are modified.

This is useful when merging standalone column lists into multi-table queries to avoid ambiguous column reference errors.

Parameters:

  • tableName: The qualifier to prepend to unqualified identifiers

Returns ErrUnsupportedStatement for non-SELECT statements.

func RemoveColumn

func RemoveColumn(name string) Rule

RemoveColumn returns a Rule that removes the first column in the SELECT list that matches name. Matching is case-insensitive and checks both identifier names and expression aliases.

Returns an error if no column matching name is found, or ErrUnsupportedStatement for non-SELECT statements.

func RemoveJoin

func RemoveJoin(tableName string) Rule

RemoveJoin returns a Rule that removes all JOIN clauses whose right-hand table name or alias matches tableName (case-insensitive). If no matching JOIN exists the statement is returned unmodified without an error.

Parameters:

  • tableName: Name or alias of the table to remove from the JOIN list

Returns ErrUnsupportedStatement for non-SELECT statements.

func RemoveLimit

func RemoveLimit() Rule

RemoveLimit returns a Rule that removes the LIMIT clause from a SELECT statement, allowing the query to return an unbounded number of rows.

Returns ErrUnsupportedStatement for non-SELECT statements.

func RemoveOffset

func RemoveOffset() Rule

RemoveOffset returns a Rule that removes the OFFSET clause from a SELECT statement, resetting pagination to start from the first row.

Returns ErrUnsupportedStatement for non-SELECT statements.

func RemoveOrderBy

func RemoveOrderBy() Rule

RemoveOrderBy returns a Rule that removes the ORDER BY clause entirely from a SELECT statement, leaving the result set in an unspecified (engine-dependent) order. This is useful when rewriting queries for intermediate stages in a pipeline where ordering should be deferred to the final step, or when performance is preferred over a stable row order.

Returns ErrUnsupportedStatement for non-SELECT statements.

func RemoveWhere

func RemoveWhere() Rule

RemoveWhere returns a Rule that removes the WHERE clause from a SELECT, UPDATE, or DELETE statement. After the rule is applied, the statement will match all rows in the target table(s). Use with care in production to avoid unintentional full table scans or mass updates.

Returns ErrUnsupportedStatement for INSERT or DDL statements.

func ReplaceColumn

func ReplaceColumn(oldName, newName string) Rule

ReplaceColumn returns a Rule that replaces every column in the SELECT list that matches oldName with a bare *ast.Identifier for newName. Matching is case-insensitive against both identifier names and aliases.

Parameters:

  • oldName: Name or alias of the column to replace
  • newName: Replacement identifier name

Returns ErrUnsupportedStatement for non-SELECT statements.

func ReplaceTable

func ReplaceTable(oldName, newName string) Rule

ReplaceTable returns a Rule that replaces all occurrences of a table name throughout a SELECT, UPDATE, or DELETE statement. The replacement covers:

  • FROM clause table references
  • JOIN clause left and right table references
  • Column qualifiers (table.column identifiers in SELECT list, WHERE, ORDER BY)
  • The table name field of UPDATE and DELETE statements
  • Subquery FROM/JOIN references (recursively)

Matching is case-insensitive. This is useful for renaming tables, routing queries to shards, or switching between environments.

Parameters:

  • oldName: The table name to replace (case-insensitive)
  • newName: The replacement table name

Returns ErrUnsupportedStatement for INSERT or DDL statements.

func ReplaceWhere

func ReplaceWhere(condition ast.Expression) Rule

ReplaceWhere returns a Rule that unconditionally replaces the WHERE clause of a SELECT, UPDATE, or DELETE statement with the given condition. Unlike AddWhere, this discards any existing WHERE predicate instead of combining with AND.

Parameters:

  • condition: The new AST expression to use as the WHERE predicate

Returns ErrUnsupportedStatement for INSERT or DDL statements.

func SetLimit

func SetLimit(n int) Rule

SetLimit returns a Rule that sets (or replaces) the LIMIT clause of a SELECT statement. Any existing LIMIT value is overwritten.

Parameters:

  • n: Number of rows to return; must be >= 0. Returns an error for negative values.

Returns ErrUnsupportedStatement for non-SELECT statements.

func SetOffset

func SetOffset(n int) Rule

SetOffset returns a Rule that sets (or replaces) the OFFSET clause of a SELECT statement. Use together with SetLimit to implement pagination.

Parameters:

  • n: Number of rows to skip; must be >= 0. Returns an error for negative values.

Returns ErrUnsupportedStatement for non-SELECT statements.

type RuleFunc

type RuleFunc func(stmt ast.Statement) error

RuleFunc is a function type that implements the Rule interface. It allows anonymous functions and closures to be used directly as transform rules without defining a named type. All built-in rule constructors (AddWhere, AddColumn, etc.) return a RuleFunc internally.

Example:

rule := transform.RuleFunc(func(stmt ast.Statement) error {
    sel, ok := stmt.(*ast.SelectStatement)
    if !ok {
        return nil
    }
    sel.Distinct = true
    return nil
})

func (RuleFunc) Apply

func (f RuleFunc) Apply(stmt ast.Statement) error

Apply implements the Rule interface by invoking the underlying function.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL