zoobzio December 12, 2025 Edit this page

Core Concepts

ASTQL has five primitives: tables, fields, params, conditions, and builders. Understanding these unlocks the full API.

Tables

A table represents a database table. Create tables through an ASTQL instance:

users := instance.T("users")

Tables can have single-letter aliases for JOINs:

users := instance.T("users", "u")  // "users" aliased as "u"
posts := instance.T("posts", "p")  // "posts" aliased as "p"

Aliases must be single lowercase letters (a-z). This restriction prevents injection through alias names.

instance.T("users", "u")           // Valid
instance.T("users", "users_alias") // Panics: invalid alias

Fields

A field represents a column. Create fields through an ASTQL instance:

email := instance.F("email")
username := instance.F("username")

Fields can be prefixed with a table alias:

userEmail := instance.WithTable(instance.F("email"), "u")
// Renders as: u."email"

Field Validation

Fields are validated against the schema. Any field name must exist in at least one table:

instance.F("email")           // Valid if "email" exists in any table
instance.F("nonexistent")     // Panics: field not found in schema

Params

A param represents a query parameter. All user values flow through params:

emailParam := instance.P("email_value")
activeParam := instance.P("is_active")

Params are rendered as named placeholders:

// In SQL: WHERE "email" = :email_value

Parameter Validation

Parameter names must be valid SQL identifiers:

instance.P("user_id")                    // Valid
instance.P("id; DROP TABLE users--")     // Panics: invalid parameter

Conditions

Conditions represent WHERE clause predicates. Create conditions with instance.C:

condition := instance.C(
    instance.F("email"),     // Field
    astql.EQ,                // Operator
    instance.P("email_val"), // Param
)

Operators

ConstantSQLDescription
EQ=Equals
NE!=Not equals
GT>Greater than
GE>=Greater than or equal
LT<Less than
LE<=Less than or equal
LIKELIKEPattern match
ILIKEILIKECase-insensitive pattern match
IN= ANY()In array
NotIn!= ALL()Not in array
IsNullIS NULLNull check
IsNotNullIS NOT NULLNot null check

See Operators Reference for the complete list.

Combining Conditions

Use And and Or to combine conditions:

// AND: both must be true
instance.And(
    instance.C(instance.F("active"), astql.EQ, instance.P("is_active")),
    instance.C(instance.F("verified"), astql.EQ, instance.P("is_verified")),
)
// Renders: ("active" = :is_active AND "verified" = :is_verified)

// OR: either must be true
instance.Or(
    instance.C(instance.F("role"), astql.EQ, instance.P("admin_role")),
    instance.C(instance.F("role"), astql.EQ, instance.P("mod_role")),
)
// Renders: ("role" = :admin_role OR "role" = :mod_role)

Nested Conditions

Conditions can be nested arbitrarily:

instance.And(
    instance.C(instance.F("active"), astql.EQ, instance.P("is_active")),
    instance.Or(
        instance.C(instance.F("role"), astql.EQ, instance.P("admin")),
        instance.C(instance.F("role"), astql.EQ, instance.P("mod")),
    ),
)
// Renders: ("active" = :is_active AND ("role" = :admin OR "role" = :mod))

NULL Conditions

Use Null and NotNull for NULL checks:

instance.Null(instance.F("deleted_at"))     // "deleted_at" IS NULL
instance.NotNull(instance.F("verified_at")) // "verified_at" IS NOT NULL

Field Comparisons

Compare two fields directly with CF:

astql.CF(
    instance.F("created_at"),
    astql.LT,
    instance.F("updated_at"),
)
// Renders: "created_at" < "updated_at"

Builders

Builders construct queries through method chaining. Each operation type has its own entry point:

astql.Select(table)   // SELECT query
astql.Insert(table)   // INSERT query
astql.Update(table)   // UPDATE query
astql.Delete(table)   // DELETE query
astql.Count(table)    // SELECT COUNT(*) query

Fluent API

Methods return the builder for chaining:

result, err := astql.Select(instance.T("users")).
    Fields(instance.F("username"), instance.F("email")).
    Where(condition).
    OrderBy(instance.F("username"), astql.ASC).
    Limit(10).
    Offset(20).
    Render(postgres.New())

Build vs Render

  • Build() returns the AST for inspection or modification
  • Render(provider) produces the final SQL and parameter list using the specified provider
// Get the AST
ast, err := query.Build()

// Get SQL output
result, err := query.Render(postgres.New())

Must Variants

MustBuild and MustRender panic on error instead of returning it:

result := query.MustRender(postgres.New())  // Panics if invalid

Try Variants

All instance methods have Try variants that return errors instead of panicking:

// Panics on invalid input
table := instance.T("users")
field := instance.F("email")
param := instance.P("value")

// Returns error on invalid input
table, err := instance.TryT("users")
field, err := instance.TryF("email")
param, err := instance.TryP("value")

Use Try variants when handling user input or dynamic field names.

Query Result

Render() returns a QueryResult:

type QueryResult struct {
    SQL            string   // The rendered SQL query
    RequiredParams []string // Parameter names that must be provided
}

The RequiredParams slice lists all parameters in order of appearance. Use this to validate that all required values are provided before execution.