Mutations¶
GuardSQL supports trusted, application-authored mutations (INSERT/UPDATE/DELETE) through a programmatic builder. Trusted code constructs a statement from typed parts — it does not parse mutation SQL text — and compiles it through the same hardened path as reads: values are bound as parameters, identifiers are resolved against the schema and re-quoted, and the operation must be permitted by policy.
Use mutations only with a trusted Config; customer-supplied SQL must never reach this path.
Assignment targets must be columns marked Writable in the schema (a
deny-by-default allowlist, orthogonal to the read capabilities):
"status": {Type: guardsql.FieldString, Selectable: true, Writable: true},
"id": {Type: guardsql.FieldNumber, Selectable: true}, // read-only, not assignable
Builders¶
cfg := guardsql.NewTrustedConfig(schema, guardsql.DialectPostgres)
// INSERT
guardsql.Insert("users").
Set("email", email).
Set("status", "active").
Compile(cfg)
// INSERT INTO "users" ("email", "status") VALUES ($1, $2) args: [email, "active"]
// UPDATE
guardsql.Update("users").
Set("status", "active").
Where(guardsql.Eq("id", 42)).
Compile(cfg)
// UPDATE "users" SET "status" = $1 WHERE "id" = $2 args: ["active", 42]
// DELETE
guardsql.Delete("users").
Where(guardsql.And(guardsql.Eq("status", "disabled"), guardsql.Lt("id", 100))).
Compile(cfg)
// DELETE FROM "users" WHERE ("status" = $1 AND "id" < $2) args: ["disabled", 100]
Condition Helpers¶
Build WHERE clauses with typed helpers; every value becomes a bind parameter.
| Helper | SQL |
|---|---|
Eq(f, v) / Ne(f, v) |
f = v / f != v |
Lt / Lte / Gt / Gte |
f < v, f <= v, f > v, f >= v |
In(f, v...) |
f IN (...) |
IsNull(f) / IsNotNull(f) |
f IS NULL / f IS NOT NULL |
And(...) / Or(...) / Not(e) |
boolean composition |
Safety Rules¶
- Deny by default. A mutation is compiled only if its operation is
explicitly listed in the Config's
Policy.AllowedOps. A zeroConfigandNewUntrustedConfigboth permit no mutations; onlyNewTrustedConfig(or an explicitAllowedOps) enables them. WHERErequired.UpdateandDeletereject a missingWHEREunlessConfig.AllowUnfilteredMutationsis set — no accidental full-table writes.- Parameterized values. A value such as
x'; DROP TABLE users; --is a bound argument, never inlined SQL. - Writable allowlist (mass-assignment defense). An assignment target must be
a column explicitly marked
Writablein the schema.Writableis deny-by-default, so a caller cannot write a column such asroleoris_adminunless the schema permits it — even when the field is readable. - Row scope is enforced.
UPDATEandDELETEAND the configuredRowScopeinto theWHERE;INSERTstamps the scope's equality terms onto the new row. Columns referenced by the scope cannot be assigned, so a row cannot be written into or moved out of the caller's scope. See Row Scope. - Schema-checked identifiers. Unknown columns are rejected; assignment targets must be plain columns (not JSON paths); identifier lockdown applies.
Why a Builder Instead of Parsed SQL¶
For internal calls, constructing typed AST nodes is safer than parsing SQL strings: there is no mutation grammar to attack, and the compiler guarantees parameterization and identifier safety. A mutation SQL parser may be added later if routing existing trusted SQL strings through GuardSQL becomes useful.