Skip to content

GuardSQL

GuardSQL is a small, application-neutral query language for SaaS analytics and reporting. It gives users a SQL-like interface without passing customer-authored SQL directly to a database.

The core contract is:

query text -> parser -> AST -> schema validation -> policy -> backend compiler

Applications own tenant scope, authorization, execution limits, audit logging, and backend compilation. GuardSQL owns parsing, AST types, validation helpers, policy checks, and a limited in-memory evaluator for simple cases.

This repository is the Go implementation of that engine. Language/runtime integrations should be separate from the root module. For example, a Hibernate adapter should live in a Java-oriented repository such as github.com/grokify/guardsql-java or github.com/grokify/guardsql-hibernate and consume the same conceptual AST/policy contract without adding Java dependencies here.

Design Goals

  • Keep customer query text away from direct database execution.
  • Make unsupported syntax fail as a normal parse error.
  • Validate entities and fields against explicit allowlisted schemas.
  • Enforce read-only and field-level policy against the AST.
  • Keep the root Go module dependency-light.
  • Put heavyweight authorization integrations in optional modules.
  • Keep non-Go runtime integrations outside this repository.

Core Packages

Package Purpose
github.com/grokify/guardsql Parser, AST, schema validation, policy, evaluator
github.com/grokify/guardsql/authz Lightweight authorization decision helper
github.com/grokify/guardsql/authzsystemforge Optional SystemForge/SpiceDB adapter module

Minimal Example

q, err := guardsql.Parse(`SELECT id, name FROM roadmap_items WHERE status = "planned" LIMIT 25`)
if err != nil {
    return err
}

if err := guardsql.Validate(q, schema); err != nil {
    return err
}

issues := guardsql.CheckPolicy(q, guardsql.Policy{
    AllowedOps: []guardsql.Operation{guardsql.OperationRead},
    MaxDepth:   8,
    MaxNodes:   80,
})
if len(issues) > 0 {
    return fmt.Errorf("query not allowed: %s", issues[0].Message)
}