Documentation
¶
Overview ¶
Package gxsql validates database table contents through database/sql without loading whole tables into application memory.
Build a suite from expectations, then validate a table with the dialect that matches the database connection:
suite := gxsql.NewSuite(
gxsql.RowCount().GreaterOrEqual(1),
gxsql.Int("age").Between(0, 120),
gxsql.String("email").NotEmpty(),
)
report, err := suite.ValidateTable(ctx, db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.Postgres()),
)
if err != nil {
// Configuration or execution error; no complete report is available.
}
if err := report.Err(); err != nil {
// Validation completed, but one or more policies failed.
}
For scoped validation, use TrustedScope with WithScope. The scope predicate limits every expectation to matching rows and uses dialect-neutral ? placeholders; each value is bound separately through the arguments:
tenantID := "tenant-acme"
scope := gxsql.TrustedScope("tenant-acme", "tenant_id = ?", tenantID)
report, err := suite.ValidateTable(ctx, db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.Postgres()),
gxsql.WithScope(scope),
)
Scope predicates are trusted Go-code input, not a sandbox for untrusted SQL. Callers must not pass user-authored predicate text. Report.ScopeID and the exported scope.id carry caller identity only; default errors and display output, and ExportReport, do not serialize the scope predicate text or bound arguments. Other report samples remain subject to their usual redaction requirements. In production, use a read-only database role and a context deadline for each validation.
Custom count checks use TrustedCountQuery and CustomCount. The SQL template is trusted Go-code input reviewed by the application, not a sandbox for untrusted text; callers must never insert user-authored SQL into templates. A template contains exactly one {{target}} and one {{scope}}, both outside SQL strings and comments. The library renders {{target}} from the validated TableRef and {{scope}} from WithScope (or TRUE when unscoped). Custom ? placeholders must follow {{scope}}; bound arguments are scope values first, then custom values. The query returns one row and one column. Signed integer driver values (int through int64) are accepted; textual numerics are not coerced. The count must be non-negative and uses RowDenominatorUnavailable: no total, percentage, samples, or failed keys. Default reports, errors, and ExportReport omit template SQL and arguments, including driver-error text. CaptureQueryDiagnostics is an opt-in export-only path subject to existing redactors.
Bounded failure tolerance uses WithMaxFailedCount around an eligible per-row or uniqueness expectation. max is an inclusive non-negative failed-row bound; equality passes. Tolerance changes only the policy verdict—raw Total, FailedCount, FailedPercent, samples, and failed keys stay under existing caps. Gate with Report.Err; tolerated results count as successful and are omitted from Report.Failures, so inspect Report.Results and Result.Tolerated for remediation. Table-level, aggregate, distinct-count, row-count, and custom-count wrappers fail preflight. Execution and configuration errors are never tolerated:
suite := gxsql.NewSuite(
gxsql.WithMaxFailedCount(2, gxsql.String("email").NotEmpty()),
)
report, err := suite.ValidateTable(ctx, db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.Postgres()),
)
if err != nil {
// Configuration or execution error; no complete report is available.
}
if err := report.Err(); err != nil {
// Above-bound or other policy failures.
}
Failed policies are collected in declaration order. Use WithKey to retain failed-row identities, WithID to give expectations stable machine identity, and ExportReport for the versioned JSON DTO. For Go tests, use the gxsqltest subpackage's Check or Require helper.
Example ¶
package main
import (
"context"
"fmt"
"github.com/busyminds/gxsql"
"github.com/busyminds/gxsql/gxsqltest"
)
type recorderT struct {
errors int
fatals int
}
func (r *recorderT) Helper() {}
func (r *recorderT) Errorf(string, ...any) {
r.errors++
}
func (r *recorderT) Fatalf(string, ...any) {
r.fatals++
}
func main() {
checkT := &recorderT{}
fmt.Println(gxsqltest.Check(
checkT,
context.Background(),
gxsql.NewSuite(gxsql.Column("status").In()),
nil,
gxsql.Table("users"),
gxsql.WithDialect(gxsql.Postgres()),
))
fmt.Println(checkT.errors, checkT.fatals)
requireT := &recorderT{}
gxsqltest.Require(
requireT,
context.Background(),
gxsql.NewSuite(gxsql.Column("status").In()),
nil,
gxsql.Table("users"),
gxsql.WithDialect(gxsql.Postgres()),
)
fmt.Println(requireT.errors, requireT.fatals)
}
Output: false 1 0 0 1
Index ¶
- Constants
- Variables
- type CategorizedError
- type ColumnBuilder
- type CountQuery
- type DB
- type Dialect
- type DistinctCountBuilder
- func (b DistinctCountBuilder) Between(lo, hi int) Expectation
- func (b DistinctCountBuilder) Equal(want int) Expectation
- func (b DistinctCountBuilder) GreaterOrEqual(bound int) Expectation
- func (b DistinctCountBuilder) GreaterThan(bound int) Expectation
- func (b DistinctCountBuilder) LessOrEqual(bound int) Expectation
- func (b DistinctCountBuilder) LessThan(bound int) Expectation
- type ErrorCategory
- type ExecutionOutcome
- type Expectation
- type ExpectationKind
- type ExportOption
- func IncludeCapturedArguments() ExportOption
- func IncludeCapturedDiagnostics() ExportOption
- func IncludeFailedKeys() ExportOption
- func IncludeSamples() ExportOption
- func WithArgsRedactor(fn Redactor) ExportOption
- func WithKeyRedactor(fn Redactor) ExportOption
- func WithQueryRedactor(fn Redactor) ExportOption
- func WithSampleRedactor(fn Redactor) ExportOption
- type ExportedCaps
- type ExportedCounts
- type ExportedDiagnostics
- type ExportedError
- type ExportedFacts
- type ExportedReport
- type ExportedResult
- type ExportedScope
- type ExportedTarget
- type NormalizedValue
- type NumberColumn
- func (c NumberColumn) AverageBetween(lo, hi float64) Expectation
- func (c NumberColumn) Between(lo, hi any) Expectation
- func (c NumberColumn) GreaterOrEqual(bound any) Expectation
- func (c NumberColumn) GreaterThan(bound any) Expectation
- func (c NumberColumn) LessOrEqual(bound any) Expectation
- func (c NumberColumn) LessThan(bound any) Expectation
- func (c NumberColumn) MaxLessOrEqual(bound float64) Expectation
- func (c NumberColumn) MinGreaterOrEqual(bound float64) Expectation
- type Option
- type PolicyVerdict
- type PreflightErrors
- type PreflightIssue
- type Redactor
- type Report
- type Result
- type ResultFacts
- type RowCountBuilder
- func (RowCountBuilder) Between(lo, hi int) Expectation
- func (RowCountBuilder) Equal(want int) Expectation
- func (RowCountBuilder) GreaterOrEqual(bound int) Expectation
- func (RowCountBuilder) GreaterThan(bound int) Expectation
- func (RowCountBuilder) LessOrEqual(bound int) Expectation
- func (RowCountBuilder) LessThan(bound int) Expectation
- type RowDenominator
- type RowKey
- type Scope
- type StringColumn
- type Suite
- type TableRef
- type ValidationError
Examples ¶
Constants ¶
const DefaultFailedKeysCap = 100
DefaultFailedKeysCap is the default maximum failed-row keys retained per Result. Use WithFailedKeysCap(0) for unlimited retention when every failing key is required for remediation.
const DefaultSampleCap = 20
DefaultSampleCap is the default maximum offending sample values retained per failing per-row result. Override per suite with Suite.WithSampleCap or per run with WithSampleCap. A cap of zero disables sample collection.
const ExportSchemaVersion = "gxsql.report.v1"
ExportSchemaVersion is the top-level schema version for ExportedReport JSON.
const MaxExportedArgumentCount = 256
MaxExportedArgumentCount is the maximum number of exported bound arguments.
const MaxExportedErrorMessageRunes = 512
MaxExportedErrorMessageRunes is the maximum rune length of export-safe errors.
const MaxExportedQueryTextRunes = 4096
MaxExportedQueryTextRunes is the maximum rune length of exported SQL text.
Variables ¶
var ( ErrCategoryInvalidConfig = &categoryMarker{CategoryInvalidConfig} ErrCategoryUnsupported = &categoryMarker{CategoryUnsupported} ErrCategoryRendering = &categoryMarker{CategoryRendering} ErrCategoryDatabase = &categoryMarker{CategoryDatabase} ErrCategoryScan = &categoryMarker{CategoryScan} ErrCategoryContext = &categoryMarker{CategoryContext} ErrCategoryObserver = &categoryMarker{CategoryObserver} )
ErrCategory* values are category markers for errors.Is against a typed ErrorCategory. For example, errors.Is(err, ErrCategoryInvalidConfig) reports whether err is or wraps a CategorizedError with that category.
Functions ¶
This section is empty.
Types ¶
type CategorizedError ¶
type CategorizedError struct {
// Category is the machine-facing failure class.
Category ErrorCategory
// Err is the underlying cause exposed through Unwrap.
Err error
}
CategorizedError attaches a stable category to an underlying failure.
func (*CategorizedError) Error ¶
func (e *CategorizedError) Error() string
Error returns a diagnostic message including the category and wrapped error.
func (*CategorizedError) Is ¶
func (e *CategorizedError) Is(target error) bool
Is reports whether target matches the category marker or unwrap chain. Category markers match by ErrorCategory; other targets delegate to Err.
func (*CategorizedError) Unwrap ¶
func (e *CategorizedError) Unwrap() error
Unwrap returns the underlying error for errors.Is and errors.As traversal.
type ColumnBuilder ¶
type ColumnBuilder struct {
// contains filtered or unexported fields
}
ColumnBuilder is the entry point for column checks that apply to any SQL type. Construct one with Column. Most methods return per-row Expectation values evaluated with RowDenominatorAvailable; ColumnBuilder.DistinctCount returns a table-level expectation evaluated with RowDenominatorUnavailable. Invalid column identifiers fail suite preflight before SQL runs.
func Column ¶
func Column(name string) ColumnBuilder
Column returns a builder for nullability, membership, uniqueness, and distinct-count checks on name. name must satisfy Dialect identifier rules.
func (ColumnBuilder) DistinctCount ¶
func (c ColumnBuilder) DistinctCount() DistinctCountBuilder
DistinctCount starts a distinct-count expectation builder for the column.
func (ColumnBuilder) In ¶
func (c ColumnBuilder) In(vals ...any) Expectation
In asserts the column value is one of vals. SQL NULL in the column fails. Each entry in vals must be non-nil; an empty list or a nil entry returns a configuration error. Very large lists expand the query (many placeholders); keep lists in the low thousands or use a lookup-table join outside gxsql.
func (ColumnBuilder) IsNull ¶
func (c ColumnBuilder) IsNull() Expectation
IsNull returns a per-row expectation that the column is SQL NULL. Non-NULL values fail. An empty table passes vacuously. Results use KindIsNull.
func (ColumnBuilder) NotIn ¶
func (c ColumnBuilder) NotIn(vals ...any) Expectation
NotIn asserts the column value is not one of vals. SQL NULL in the column fails. Each entry in vals must be non-nil; an empty list or a nil entry returns a configuration error. Very large lists expand the query (many placeholders); keep lists in the low thousands or split checks.
func (ColumnBuilder) NotNull ¶
func (c ColumnBuilder) NotNull() Expectation
NotNull returns a per-row expectation that the column is not SQL NULL. NULL values fail. An empty table passes vacuously. Results use KindNotNull.
func (ColumnBuilder) Unique ¶
func (c ColumnBuilder) Unique() Expectation
Unique returns a per-row expectation that each non-NULL value appears at most once. NULL values are ignored and do not participate in duplicate detection. Every row in a duplicate group fails. An empty table passes vacuously. Failing rows may include sample values (capped by DefaultSampleCap or WithSampleCap) and failed keys when WithKey is set. Results use KindUnique.
type CountQuery ¶ added in v0.2.0
type CountQuery struct {
// contains filtered or unexported fields
}
CountQuery is an immutable trusted SQL count template with bound arguments. Construct it with TrustedCountQuery. Validation is deferred until the query is used with CustomCount and evaluated by Suite.ValidateTable.
func TrustedCountQuery ¶ added in v0.2.0
func TrustedCountQuery(template string, args ...any) CountQuery
TrustedCountQuery returns an immutable CountQuery from trusted Go-code SQL template input and bound argument values. The template must contain exactly one {{target}} and one {{scope}} marker; validation is deferred until Suite.ValidateTable.
Example ¶
package main
import (
"fmt"
"github.com/busyminds/gxsql"
)
func main() {
query := gxsql.TrustedCountQuery(
"SELECT COUNT(*) FROM {{target}} WHERE {{scope}} AND status = ?",
"pending",
)
exp := gxsql.CustomCount("violating orders", query)
fmt.Println(exp.Name())
}
Output: violating orders
type DB ¶
type DB interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
DB is the narrow database interface Suite.ValidateTable executes against. Implementations must honor context cancellation on context.Context.
type Dialect ¶
type Dialect interface {
QuoteIdent(name string) (string, error)
Placeholder(n int) string
StringLength(expr string) string
}
Dialect renders dialect-specific SQL fragments for identifiers, bound parameters, and string-length expressions. Pass a concrete implementation to WithDialect. [QuoteIdent] must reject empty or invalid names. A nil dialect is a run-level configuration error before preflight or SQL, not a rendering-time error.
func DuckDB ¶
func DuckDB() Dialect
DuckDB returns the DuckDB Dialect. Identifiers are double-quoted after validation; placeholders are positional $n; string length uses LENGTH. Pair with WithDialect when validating DuckDB tables.
func MySQL ¶
func MySQL() Dialect
MySQL returns the MySQL Dialect. Identifiers are backtick-quoted after validation; placeholders are ?; string length uses CHAR_LENGTH. Pair with WithDialect when validating MySQL tables.
func Postgres ¶
func Postgres() Dialect
Postgres returns the PostgreSQL Dialect. Identifiers are double-quoted after validation; placeholders are positional $n; string length uses CHAR_LENGTH. Pair with WithDialect when validating PostgreSQL tables.
func SQLite ¶
func SQLite() Dialect
SQLite returns the SQLite Dialect. Identifiers are double-quoted after validation; placeholders are ?; string length uses LENGTH. Pair with WithDialect when validating SQLite tables.
type DistinctCountBuilder ¶
type DistinctCountBuilder struct {
// contains filtered or unexported fields
}
DistinctCountBuilder builds table-level COUNT(DISTINCT column) expectations. Start from ColumnBuilder.DistinctCount. SQL NULL values are excluded from the count; an empty table or all-NULL column yields 0. Results set RowDenominatorUnavailable, append the observed count to Name, and store it in ResultFacts.ObservedCount. Invalid column identifiers fail suite preflight before SQL runs.
func (DistinctCountBuilder) Between ¶
func (b DistinctCountBuilder) Between(lo, hi int) Expectation
Between asserts lo <= distinct count <= hi (inclusive).
func (DistinctCountBuilder) Equal ¶
func (b DistinctCountBuilder) Equal(want int) Expectation
Equal asserts the column has exactly want distinct non-null values.
func (DistinctCountBuilder) GreaterOrEqual ¶
func (b DistinctCountBuilder) GreaterOrEqual(bound int) Expectation
GreaterOrEqual asserts distinct count >= bound.
func (DistinctCountBuilder) GreaterThan ¶
func (b DistinctCountBuilder) GreaterThan(bound int) Expectation
GreaterThan asserts distinct count > bound.
func (DistinctCountBuilder) LessOrEqual ¶
func (b DistinctCountBuilder) LessOrEqual(bound int) Expectation
LessOrEqual asserts distinct count <= bound.
func (DistinctCountBuilder) LessThan ¶
func (b DistinctCountBuilder) LessThan(bound int) Expectation
LessThan asserts distinct count < bound.
type ErrorCategory ¶
type ErrorCategory string
ErrorCategory is a closed vocabulary of machine-facing failure classes.
const ( // CategoryInvalidConfig marks expectation or run configuration rejected // before or during validation setup. CategoryInvalidConfig ErrorCategory = "invalid_config" // CategoryUnsupported marks a requested capability the library does not provide. CategoryUnsupported ErrorCategory = "unsupported" // CategoryRendering marks SQL identifier or fragment rendering failures. CategoryRendering ErrorCategory = "rendering" // CategoryDatabase marks database/sql execution failures unrelated to scanning. CategoryDatabase ErrorCategory = "database" // CategoryScan marks row iteration or column scan failures. CategoryScan ErrorCategory = "scan" // CategoryContext marks context cancellation or deadline exceeded. CategoryContext ErrorCategory = "context" // CategoryObserver marks export redaction or normalization failures. CategoryObserver ErrorCategory = "observer" )
type ExecutionOutcome ¶
type ExecutionOutcome string
ExecutionOutcome classifies how validation ran, distinct from policy verdict.
const ( // ExecutionOutcomeOK means the expectation ran and its policy passed. ExecutionOutcomeOK ExecutionOutcome = "ok" // ExecutionOutcomePolicyFailure means the expectation ran and its policy failed. ExecutionOutcomePolicyFailure ExecutionOutcome = "policy_failure" // ExecutionOutcomeExecutionFailure means execution failed after evaluation began. ExecutionOutcomeExecutionFailure ExecutionOutcome = "execution_failure" // ExecutionOutcomeConfigFailure means preflight configuration prevented execution. ExecutionOutcomeConfigFailure ExecutionOutcome = "config_failure" )
type Expectation ¶
type Expectation interface {
Name() string
// contains filtered or unexported methods
}
Expectation is the sealed unit of SQL validation over a table. Construct expectations with the column, row-count, and aggregate builders; its unexported methods prevent implementations outside package gxsql. The [Name] method supplies display text, while Suite.ValidateTable reports a library-defined Result.Kind. Attach a stable result ID with WithID.
func CustomCount ¶ added in v0.2.0
func CustomCount(name string, query CountQuery) Expectation
CustomCount returns an expectation that executes query and treats the returned count as the expectation-specific failure count. Results use KindCustom with RowDenominatorUnavailable and expose Result.FailedCount without a row denominator, samples, or failed keys. name must be non-blank; validation is deferred until Suite.ValidateTable.
Example ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"sync"
"github.com/busyminds/gxsql"
)
const exampleDriverName = "gxsql_example_test"
var registerExampleDriverOnce sync.Once
func openExampleDB(scenario string) (*sql.DB, error) {
registerExampleDriverOnce.Do(func() {
sql.Register(exampleDriverName, exampleDriver{})
})
return sql.Open(exampleDriverName, scenario)
}
type exampleDriver struct{}
func (exampleDriver) Open(name string) (driver.Conn, error) {
return exampleConn{scenario: name}, nil
}
type exampleConn struct {
scenario string
}
func (c exampleConn) Prepare(string) (driver.Stmt, error) { return nil, driver.ErrSkip }
func (c exampleConn) Close() error { return nil }
func (c exampleConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip }
func (c exampleConn) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
switch c.scenario {
case "pass":
return c.passQuery(query, args)
case "keys":
return c.keysQuery(query, args)
case "customcount":
return c.customCountQuery(query, args)
case "error":
return nil, fmt.Errorf("example driver: query failed")
default:
return nil, fmt.Errorf("example driver: unknown scenario %q", c.scenario)
}
}
func (exampleConn) passQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(0)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func (exampleConn) customCountQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "orders" WHERE TRUE AND status = ?`:
if err := expectArgs(args, "pending"); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected custom count query")
}
}
func (exampleConn) keysQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(3)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT "id" FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ? ORDER BY "id"`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"id"}, []driver.Value{int64(2)}, []driver.Value{int64(3)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func expectArgs(args []driver.NamedValue, want ...any) error {
if len(args) != len(want) {
return fmt.Errorf("example driver: arg count = %d, want %d", len(args), len(want))
}
for i := range want {
if args[i].Value != want[i] {
return fmt.Errorf("example driver: arg %d = %v, want %v", i, args[i].Value, want[i])
}
}
return nil
}
type exampleRows struct {
cols []string
rows [][]driver.Value
idx int
}
func newExampleRows(cols []string, rows ...[]driver.Value) driver.Rows {
return &exampleRows{cols: cols, rows: rows}
}
func (r *exampleRows) Columns() []string { return r.cols }
func (r *exampleRows) Close() error { return nil }
func (r *exampleRows) Next(dest []driver.Value) error {
if r.idx >= len(r.rows) {
return io.EOF
}
copy(dest, r.rows[r.idx])
r.idx++
return nil
}
func main() {
db, err := openExampleDB("customcount")
if err != nil {
panic(err)
}
defer db.Close()
query := gxsql.TrustedCountQuery(
"SELECT COUNT(*) FROM {{target}} WHERE {{scope}} AND status = ?",
"pending",
)
suite := gxsql.NewSuite(
gxsql.CustomCount("violating orders", query),
)
report, err := suite.ValidateTable(
context.Background(), db, gxsql.Table("orders"),
gxsql.WithDialect(gxsql.SQLite()),
)
if err != nil {
panic(err)
}
fmt.Println(report.OK())
fmt.Println(report.Results[0].FailedCount)
}
Output: false 2
func WithID ¶
func WithID(id string, exp Expectation) Expectation
WithID decorates an expectation with a caller-supplied stable Result.ID. Blank or whitespace-only IDs are rejected during suite preflight. Duplicate IDs across one suite are also rejected before SQL runs. Wrapped behavior, Result.Kind, and display Result.Name are preserved; IDs are never derived from Name.
func WithMaxFailedCount ¶ added in v0.2.0
func WithMaxFailedCount(max int, exp Expectation) Expectation
WithMaxFailedCount wraps an eligible expectation with an inclusive maximum failed-row allowance. Only per-row and uniqueness expectations qualify; table-level, aggregate, distinct-count, row-count, and custom-count declarations are rejected at ValidateTable preflight. Negative max values are also rejected at preflight. The wrapper is immutable and may nest with WithID in either order.
Tolerance changes only the policy verdict. Raw Total, FailedCount, FailedPercent, SampleValues, and FailedKeys are preserved under their existing caps. A nonzero raw failure count at or below max yields Success true and Tolerated true; raw-zero and empty populations pass without being tolerated; above-bound and error results cannot be tolerated.
Example ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"sync"
"github.com/busyminds/gxsql"
)
const exampleDriverName = "gxsql_example_test"
var registerExampleDriverOnce sync.Once
func openExampleDB(scenario string) (*sql.DB, error) {
registerExampleDriverOnce.Do(func() {
sql.Register(exampleDriverName, exampleDriver{})
})
return sql.Open(exampleDriverName, scenario)
}
type exampleDriver struct{}
func (exampleDriver) Open(name string) (driver.Conn, error) {
return exampleConn{scenario: name}, nil
}
type exampleConn struct {
scenario string
}
func (c exampleConn) Prepare(string) (driver.Stmt, error) { return nil, driver.ErrSkip }
func (c exampleConn) Close() error { return nil }
func (c exampleConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip }
func (c exampleConn) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
switch c.scenario {
case "pass":
return c.passQuery(query, args)
case "keys":
return c.keysQuery(query, args)
case "customcount":
return c.customCountQuery(query, args)
case "error":
return nil, fmt.Errorf("example driver: query failed")
default:
return nil, fmt.Errorf("example driver: unknown scenario %q", c.scenario)
}
}
func (exampleConn) passQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(0)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func (exampleConn) customCountQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "orders" WHERE TRUE AND status = ?`:
if err := expectArgs(args, "pending"); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected custom count query")
}
}
func (exampleConn) keysQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(3)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT "id" FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ? ORDER BY "id"`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"id"}, []driver.Value{int64(2)}, []driver.Value{int64(3)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func expectArgs(args []driver.NamedValue, want ...any) error {
if len(args) != len(want) {
return fmt.Errorf("example driver: arg count = %d, want %d", len(args), len(want))
}
for i := range want {
if args[i].Value != want[i] {
return fmt.Errorf("example driver: arg %d = %v, want %v", i, args[i].Value, want[i])
}
}
return nil
}
type exampleRows struct {
cols []string
rows [][]driver.Value
idx int
}
func newExampleRows(cols []string, rows ...[]driver.Value) driver.Rows {
return &exampleRows{cols: cols, rows: rows}
}
func (r *exampleRows) Columns() []string { return r.cols }
func (r *exampleRows) Close() error { return nil }
func (r *exampleRows) Next(dest []driver.Value) error {
if r.idx >= len(r.rows) {
return io.EOF
}
copy(dest, r.rows[r.idx])
r.idx++
return nil
}
func main() {
db, err := openExampleDB("keys")
if err != nil {
panic(err)
}
defer db.Close()
report, err := gxsql.NewSuite(
gxsql.WithMaxFailedCount(2, gxsql.Int("age").Between(0, 120)),
).WithSampleCap(0).ValidateTable(
context.Background(), db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.SQLite()),
gxsql.WithKey("id"),
gxsql.WithFailedKeysCap(0),
)
if err != nil {
panic(err)
}
fmt.Println(report.OK())
fmt.Println(report.Results[0].Tolerated)
fmt.Println(*report.Results[0].Facts.ConfiguredMaxFailedCount)
fmt.Println(report.String())
}
Output: true true 2 gxsql report: 1/1 expectations passed ✓ age between [0,120] tolerated 2/3 failed (66.7%) e.g. [] @ [[2] [3]]
type ExpectationKind ¶
type ExpectationKind string
ExpectationKind is a stable machine identifier for a built-in expectation type. Custom expectations use KindCustom unless a decorator assigns another kind.
const ( // KindRowCountEqual marks an expectation from RowCount().Equal. KindRowCountEqual ExpectationKind = "row_count_equal" // KindRowCountBetween marks an expectation from RowCount().Between. KindRowCountBetween ExpectationKind = "row_count_between" // KindRowCountGreaterThan marks an expectation from RowCount().GreaterThan. KindRowCountGreaterThan ExpectationKind = "row_count_greater_than" // KindRowCountGreaterEqual marks an expectation from RowCount().GreaterOrEqual. KindRowCountGreaterEqual ExpectationKind = "row_count_greater_or_equal" // KindRowCountLessThan marks an expectation from RowCount().LessThan. KindRowCountLessThan ExpectationKind = "row_count_less_than" // KindRowCountLessEqual marks an expectation from RowCount().LessOrEqual. KindRowCountLessEqual ExpectationKind = "row_count_less_or_equal" // KindIsNull marks an expectation from Column().IsNull. KindIsNull ExpectationKind = "is_null" // KindNotNull marks an expectation from Column().NotNull. KindNotNull ExpectationKind = "not_null" // KindIn marks an expectation from Column().In. KindIn ExpectationKind = "in" // KindNotIn marks an expectation from Column().NotIn. KindNotIn ExpectationKind = "not_in" // KindUnique marks an expectation from Column().Unique. KindUnique ExpectationKind = "unique" // KindBetween marks an expectation from NumberColumn.Between. KindBetween ExpectationKind = "between" // KindGreaterThan marks an expectation from NumberColumn.GreaterThan. KindGreaterThan ExpectationKind = "greater_than" // KindLessThan marks an expectation from NumberColumn.LessThan. KindLessThan ExpectationKind = "less_than" // KindGreaterOrEqual marks an expectation from NumberColumn.GreaterOrEqual. KindGreaterOrEqual ExpectationKind = "greater_or_equal" // KindLessOrEqual marks an expectation from NumberColumn.LessOrEqual. KindLessOrEqual ExpectationKind = "less_or_equal" // KindNotEmpty marks an expectation from StringColumn.NotEmpty. KindNotEmpty ExpectationKind = "not_empty" // KindEmpty marks an expectation from StringColumn.Empty. KindEmpty ExpectationKind = "empty" // KindLenEqual marks an expectation from StringColumn.LenEqual. KindLenEqual ExpectationKind = "len_equal" // KindLenBetween marks an expectation from StringColumn.LenBetween. KindLenBetween ExpectationKind = "len_between" // KindDistinctCountEqual marks an expectation from DistinctCountBuilder.Equal. KindDistinctCountEqual ExpectationKind = "distinct_count_equal" // KindDistinctCountBetween marks an expectation from DistinctCountBuilder.Between. KindDistinctCountBetween ExpectationKind = "distinct_count_between" // KindDistinctCountGreaterThan marks an expectation from DistinctCountBuilder.GreaterThan. KindDistinctCountGreaterThan ExpectationKind = "distinct_count_greater_than" // KindDistinctCountGreaterEqual marks an expectation from DistinctCountBuilder.GreaterOrEqual. KindDistinctCountGreaterEqual ExpectationKind = "distinct_count_greater_or_equal" // KindDistinctCountLessThan marks an expectation from DistinctCountBuilder.LessThan. KindDistinctCountLessThan ExpectationKind = "distinct_count_less_than" // KindDistinctCountLessEqual marks an expectation from DistinctCountBuilder.LessOrEqual. KindDistinctCountLessEqual ExpectationKind = "distinct_count_less_or_equal" // KindAverageBetween marks an expectation from NumberColumn.AverageBetween. KindAverageBetween ExpectationKind = "average_between" // KindMinGreaterOrEqual marks an expectation from NumberColumn.MinGreaterOrEqual. KindMinGreaterOrEqual ExpectationKind = "min_greater_or_equal" // KindMaxLessOrEqual marks an expectation from NumberColumn.MaxLessOrEqual. KindMaxLessOrEqual ExpectationKind = "max_less_or_equal" // KindCustom is the explicit kind for expectations without built-in metadata. KindCustom ExpectationKind = "custom" )
type ExportOption ¶
type ExportOption func(*exportConfig)
ExportOption configures ExportReport.
func IncludeCapturedArguments ¶
func IncludeCapturedArguments() ExportOption
IncludeCapturedArguments exports normalized bound arguments alongside captured query text. Requires CaptureQueryDiagnostics at validation time.
func IncludeCapturedDiagnostics ¶
func IncludeCapturedDiagnostics() ExportOption
IncludeCapturedDiagnostics exports captured SQL text from results that were validated with CaptureQueryDiagnostics. Omitted by default.
func IncludeFailedKeys ¶
func IncludeFailedKeys() ExportOption
IncludeFailedKeys exports normalized FailedKeys. Omitted by default.
func IncludeSamples ¶
func IncludeSamples() ExportOption
IncludeSamples exports normalized SampleValues. Omitted by default.
func WithArgsRedactor ¶
func WithArgsRedactor(fn Redactor) ExportOption
WithArgsRedactor applies fn to each captured argument before export.
func WithKeyRedactor ¶
func WithKeyRedactor(fn Redactor) ExportOption
WithKeyRedactor applies fn to each failed key before export.
func WithQueryRedactor ¶
func WithQueryRedactor(fn Redactor) ExportOption
WithQueryRedactor applies fn to captured query text after identifiers are redacted and query text may be truncated to MaxExportedQueryTextRunes; fn runs after that initial truncation and its output is truncated again. fn must return a string; errors and panics fail export closed.
func WithSampleRedactor ¶
func WithSampleRedactor(fn Redactor) ExportOption
WithSampleRedactor applies fn to each sample value before export.
type ExportedCaps ¶
type ExportedCaps struct {
// SamplesReturned is the number of exported samples.
SamplesReturned int `json:"samples_returned,omitempty"`
// SamplesTruncated is true when more samples existed than were retained.
SamplesTruncated bool `json:"samples_truncated,omitempty"`
// KeysReturned is the number of exported failed keys.
KeysReturned int `json:"keys_returned,omitempty"`
// KeysTruncated is true when more failing keys existed than were retained.
KeysTruncated bool `json:"keys_truncated,omitempty"`
}
ExportedCaps reports diagnostic truncation metadata.
type ExportedCounts ¶
type ExportedCounts struct {
// Total is the evaluated row population; omitted when row_denominator is unavailable.
Total *int `json:"total,omitempty"`
// Failed is the number of failing rows when row_denominator is available.
Failed *int `json:"failed,omitempty"`
// FailedPercent is the percentage of failing rows; omitted when unavailable.
FailedPercent *float64 `json:"failed_percent,omitempty"`
}
ExportedCounts holds row population metrics.
type ExportedDiagnostics ¶
type ExportedDiagnostics struct {
// Query is redacted SQL text when IncludeCapturedDiagnostics is enabled.
Query string `json:"query,omitempty"`
// Args holds normalized bound arguments when IncludeCapturedArguments is enabled.
Args []NormalizedValue `json:"args,omitempty"`
// QueryTruncated is true when query text exceeded MaxExportedQueryTextRunes.
QueryTruncated bool `json:"query_truncated,omitempty"`
// ArgsTruncated is true when argument count exceeded MaxExportedArgumentCount.
ArgsTruncated bool `json:"args_truncated,omitempty"`
}
ExportedDiagnostics holds optional redacted SQL diagnostics.
type ExportedError ¶
type ExportedError struct {
// Category is the machine-facing failure class.
Category ErrorCategory `json:"category"`
// Message is diagnostic detail safe for export.
Message string `json:"message"`
}
ExportedError is a categorized export-safe error representation.
type ExportedFacts ¶
type ExportedFacts struct {
// ObservedCount is a table-level integer observation when set.
ObservedCount *int `json:"observed_count,omitempty"`
// ObservedFloat is a normalized floating-point observation when set.
ObservedFloat *NormalizedValue `json:"observed_float,omitempty"`
// ConfiguredCount is the exact-count threshold for equal-style expectations.
ConfiguredCount *int `json:"configured_count,omitempty"`
// ConfiguredCountLower and ConfiguredCountUpper are inclusive integer bounds.
ConfiguredCountLower *int `json:"configured_count_lower,omitempty"`
ConfiguredCountUpper *int `json:"configured_count_upper,omitempty"`
// ConfiguredFloatLower, ConfiguredFloatUpper, and ConfiguredFloatBound are
// thresholds for floating-point aggregate expectations.
ConfiguredFloatLower *NormalizedValue `json:"configured_float_lower,omitempty"`
ConfiguredFloatUpper *NormalizedValue `json:"configured_float_upper,omitempty"`
ConfiguredFloatBound *NormalizedValue `json:"configured_float_bound,omitempty"`
// ConfiguredBound, ConfiguredBoundLower, and ConfiguredBoundUpper hold
// per-row comparison thresholds with driver-bound types.
ConfiguredBound *NormalizedValue `json:"configured_bound,omitempty"`
ConfiguredBoundLower *NormalizedValue `json:"configured_bound_lower,omitempty"`
ConfiguredBoundUpper *NormalizedValue `json:"configured_bound_upper,omitempty"`
// ConfiguredMaxFailedCount is the inclusive WithMaxFailedCount bound when
// that decorator was applied.
ConfiguredMaxFailedCount *int `json:"configured_max_failed_count,omitempty"`
}
ExportedFacts holds structured observations and configured thresholds.
type ExportedReport ¶
type ExportedReport struct {
// SchemaVersion identifies the export contract. Always ExportSchemaVersion.
SchemaVersion string `json:"schema_version"`
// Target names the validated table when Report.Target is set; omitted when unavailable.
Target *ExportedTarget `json:"target,omitempty"`
// Scope names the validation scope when available; omitted when unavailable.
Scope *ExportedScope `json:"scope,omitempty"`
// Results preserves declaration order from Report.Results.
Results []ExportedResult `json:"results"`
}
ExportedReport is the versioned JSON DTO produced by ExportReport.
func ExportReport ¶
func ExportReport(report Report, opts ...ExportOption) (ExportedReport, error)
ExportReport converts report into a versioned JSON DTO. On error, no partial DTO is returned. Query text, bound arguments, samples, and failed keys are omitted unless explicitly enabled via ExportOption.
type ExportedResult ¶
type ExportedResult struct {
// ID is the caller-supplied stable result identifier; omitted when empty.
ID string `json:"id,omitempty"`
// Kind is the library-defined expectation kind.
Kind ExpectationKind `json:"kind"`
// DisplayName is the human-oriented result name with configured bounds redacted.
DisplayName string `json:"display_name"`
// Column is the validated column when applicable; omitted when empty.
Column string `json:"column,omitempty"`
// PolicyVerdict is pass, fail, or unevaluated when no policy verdict was produced.
PolicyVerdict PolicyVerdict `json:"policy_verdict"`
// ExecutionOutcome distinguishes policy failure from execution/config failure.
ExecutionOutcome ExecutionOutcome `json:"execution_outcome"`
// Tolerated is true when a nonzero raw failure count passed within
// WithMaxFailedCount. Omitted unless true.
Tolerated bool `json:"tolerated,omitempty"`
// RowDenominator reports whether total and failed_percent are meaningful.
RowDenominator RowDenominator `json:"row_denominator"`
// Counts holds row counts when applicable.
Counts *ExportedCounts `json:"counts,omitempty"`
// Facts holds machine-readable observations separate from display text.
Facts *ExportedFacts `json:"facts,omitempty"`
// Caps reports diagnostic truncation when samples or keys are exported.
Caps *ExportedCaps `json:"caps,omitempty"`
// Samples holds normalized failing sample values; omitted unless explicitly included.
Samples []NormalizedValue `json:"samples,omitempty"`
// FailedKeys holds normalized failing row keys; omitted unless explicitly included.
FailedKeys []NormalizedValue `json:"failed_keys,omitempty"`
// Diagnostics holds redacted query diagnostics; omitted unless explicitly included.
Diagnostics *ExportedDiagnostics `json:"diagnostics,omitempty"`
// Errors holds categorized failures in stable order; omitted when empty.
Errors []ExportedError `json:"errors,omitempty"`
}
ExportedResult is one exported expectation outcome.
type ExportedScope ¶
type ExportedScope struct {
// ID is a stable scope identifier when scope is available.
ID string `json:"id,omitempty"`
}
ExportedScope identifies a validation scope by its stable ID. Predicate and bound arguments are not included in exports.
type ExportedTarget ¶
type ExportedTarget struct {
// Schema is the optional schema qualifier; omitted when empty.
Schema string `json:"schema,omitempty"`
// Table is the table name.
Table string `json:"table"`
}
ExportedTarget identifies the table validated by ValidateTable.
type NormalizedValue ¶
type NormalizedValue struct {
// Kind identifies the encoding. One of: null, bool, string, json_integer,
// json_number, integer_string, decimal_string, bytes_base64, time_rfc3339,
// composite, non_finite, unsupported.
Kind string `json:"kind"`
// Value holds the encoded payload. Omitted when Kind is null.
Value any `json:"value,omitempty"`
// Exact reports lossless encoding. Omitted when false.
Exact bool `json:"exact,omitempty"`
}
NormalizedValue is a tagged, JSON-safe encoding of a SQL-returned scalar, bound argument, or diagnostic value. Integer magnitudes outside the exact JSON integer range use integer_string. Finite integral float64 values within the exact JSON integer range encode as json_integer with Exact true; non-integral floats use json_number, preserving signed zero as -0.0; other lossy encodings such as invalid UTF-8 strings (replaced with U+FFFD) and unsupported types (structural type name only) set Exact false.
type NumberColumn ¶
type NumberColumn struct {
// contains filtered or unexported fields
}
NumberColumn is the entry point for ordered numeric comparisons and aggregates on one column. Construct one with Int or Float.
func Float ¶
func Float(name string) NumberColumn
Float returns a builder for floating-point column checks and table-level aggregates on name. Aggregate comparisons use float64 thresholds.
func Int ¶
func Int(name string) NumberColumn
Int returns a builder for integer or general numeric column checks and table-level aggregates on name.
func (NumberColumn) AverageBetween ¶
func (c NumberColumn) AverageBetween(lo, hi float64) Expectation
AverageBetween returns a table-level expectation that AVG(column) lies in [lo, hi] (inclusive). Build it from a numeric column via Int or Float. SQL NULL values are excluded from the aggregate. When every value is NULL the check passes vacuously. Results use KindAverageBetween, set RowDenominatorUnavailable, and append the observed average to Name on evaluation. Invalid column identifiers fail suite preflight before SQL runs.
func (NumberColumn) Between ¶
func (c NumberColumn) Between(lo, hi any) Expectation
Between returns a per-row expectation that lo <= value <= hi (inclusive). SQL NULL fails. An empty table passes vacuously. Results use KindBetween.
func (NumberColumn) GreaterOrEqual ¶
func (c NumberColumn) GreaterOrEqual(bound any) Expectation
GreaterOrEqual returns a per-row expectation that value >= bound. SQL NULL fails. An empty table passes vacuously. Results use KindGreaterOrEqual.
func (NumberColumn) GreaterThan ¶
func (c NumberColumn) GreaterThan(bound any) Expectation
GreaterThan returns a per-row expectation that value > bound. SQL NULL fails. An empty table passes vacuously. Results use KindGreaterThan.
func (NumberColumn) LessOrEqual ¶
func (c NumberColumn) LessOrEqual(bound any) Expectation
LessOrEqual returns a per-row expectation that value <= bound. SQL NULL fails. An empty table passes vacuously. Results use KindLessOrEqual.
func (NumberColumn) LessThan ¶
func (c NumberColumn) LessThan(bound any) Expectation
LessThan returns a per-row expectation that value < bound. SQL NULL fails. An empty table passes vacuously. Results use KindLessThan.
func (NumberColumn) MaxLessOrEqual ¶
func (c NumberColumn) MaxLessOrEqual(bound float64) Expectation
MaxLessOrEqual returns a table-level expectation that MAX(column) <= bound. Build it from a numeric column via Int or Float. SQL NULL values are excluded. When every value is NULL the check passes vacuously. Results use KindMaxLessOrEqual, set RowDenominatorUnavailable, and append the observed maximum to Name on evaluation. Invalid column identifiers fail suite preflight before SQL runs.
func (NumberColumn) MinGreaterOrEqual ¶
func (c NumberColumn) MinGreaterOrEqual(bound float64) Expectation
MinGreaterOrEqual returns a table-level expectation that MIN(column) >= bound. Build it from a numeric column via Int or Float. SQL NULL values are excluded. When every value is NULL the check passes vacuously. Results use KindMinGreaterOrEqual, set RowDenominatorUnavailable, and append the observed minimum to Name on evaluation. Invalid column identifiers fail suite preflight before SQL runs.
type Option ¶
type Option func(*validateConfig)
Option configures a ValidateTable run.
func CaptureQueryDiagnostics ¶
func CaptureQueryDiagnostics() Option
CaptureQueryDiagnostics records SQL text and bound arguments on each Result for optional export via IncludeCapturedDiagnostics. Captured content is never exposed through default Result serialization or default export.
func ContinueOnError ¶
func ContinueOnError() Option
ContinueOnError records expectation preflight issues and execution errors on individual results and keeps evaluating later expectations. Preflight issues occupy their declaration-order slots with Result.Err set. Run-level option errors (invalid dialect, caps, key columns, or scope) still abort ValidateTable with a top-level error before evaluation starts. By default, the first preflight or execution error aborts ValidateTable with a zero report.
func SummaryOnly ¶
func SummaryOnly() Option
SummaryOnly disables complete failed-row identity and returns counts plus capped samples only.
func WithDialect ¶
WithDialect selects the SQL dialect used to render queries.
func WithFailedKeysCap ¶
WithFailedKeysCap overrides the per-result failed-key cap for one ValidateTable call. Zero means unlimited. FailedCount and FailedPercent remain complete when keys are capped.
func WithKey ¶
WithKey enables failed-row identity using the given key columns. Disables summary-only mode. Key column names must pass identifier validation. Failed keys are capped by DefaultFailedKeysCap unless WithFailedKeysCap(0) is set.
func WithSampleCap ¶
WithSampleCap overrides the per-result sample cap for one ValidateTable call. Zero disables sample collection.
type PolicyVerdict ¶
type PolicyVerdict string
PolicyVerdict is the exported policy state of an expectation: pass, fail, or unevaluated when the source Result has an error.
const ( // PolicyVerdictPass means the expectation ran and its policy passed. PolicyVerdictPass PolicyVerdict = "pass" // PolicyVerdictFail means the expectation ran and its policy failed. PolicyVerdictFail PolicyVerdict = "fail" // PolicyVerdictUnevaluated means the source Result has an execution or // configuration error, so its policy outcome is unavailable. PolicyVerdictUnevaluated PolicyVerdict = "unevaluated" )
type PreflightErrors ¶
type PreflightErrors struct {
// Issues lists every configuration failure in declaration order.
Issues []PreflightIssue
}
PreflightErrors collects every configuration issue found before SQL starts. Returned by ValidateTable when ContinueOnError is not set. Use errors.As to inspect Issues; errors.Is matches ErrCategoryInvalidConfig on each issue.
func (*PreflightErrors) Error ¶
func (e *PreflightErrors) Error() string
Error summarizes the collected configuration failures.
func (*PreflightErrors) Unwrap ¶
func (e *PreflightErrors) Unwrap() []error
Unwrap returns each issue error for multi-error inspection.
type PreflightIssue ¶
type PreflightIssue struct {
// Index is the expectation position in the suite.
Index int
// ID is the caller-supplied expectation identifier when present.
ID string
// Err is the categorized configuration error for this slot.
Err error
}
PreflightIssue records one expectation configuration failure.
type Redactor ¶
Redactor transforms a value before export. A returned error or panic fails export closed without emitting raw diagnostic content.
type Report ¶
type Report struct {
// Results preserves declaration order, including slots recorded under
// ContinueOnError.
Results []Result
// Target names the validated table. Set by ValidateTable; nil when unavailable
// (for example when a Report is assembled manually).
Target *TableRef
// ScopeID identifies the validation scope. It is empty when validation is
// unscoped.
ScopeID string
}
Report aggregates the results of every expectation in a suite.
func (Report) Err ¶
Err returns nil when the report is OK, otherwise a *ValidationError carrying the full report for gating and inspection.
func (Report) Failures ¶
Failures returns results with Success false, including configuration and execution failures recorded under ContinueOnError.
type Result ¶
type Result struct {
// ID is the optional caller-supplied stable identifier from WithID.
ID string
// Kind is the library-defined machine identifier for the expectation.
Kind ExpectationKind
// Name is human-readable display text and is not machine identity.
Name string
// Column is the validated SQL column for per-row checks and aggregates.
Column string
// Success is the policy verdict. False when the check fails or Result.Err is set.
Success bool
// RowDenominator states whether Total and FailedPercent describe rows.
RowDenominator RowDenominator
// Total is the evaluated row population when RowDenominator is available.
Total int
// FailedCount is the complete expectation-specific failure count; for per-row
// checks it counts failing rows, while CustomCount may count groups or other
// query-defined failures.
FailedCount int
// FailedPercent is the percentage of failing rows when the denominator is available.
FailedPercent float64
// Facts contains structured observations and configured thresholds.
Facts ResultFacts
// SampleValues holds capped offending column values on per-row failure.
SampleValues []any
// FailedKeys holds failing row keys in WithKey column order, capped unless
// WithFailedKeysCap(0) selects unlimited retention.
FailedKeys []RowKey
// Err is a categorized configuration or execution failure when non-nil.
Err error
// Tolerated is true when a nonzero raw FailedCount passed within a
// configured WithMaxFailedCount bound. Clean passes, above-bound failures,
// empty populations, and errors are never tolerated.
Tolerated bool
// contains filtered or unexported fields
}
Result is the outcome of one expectation over a single ValidateTable run.
Per-row SQL expectations set RowDenominatorAvailable, Total to the table row count, and populate FailedCount, FailedPercent, SampleValues, and optionally FailedKeys on failure. FailedKeys are capped unless WithFailedKeysCap(0) selects unlimited retention. Table-level checks use RowDenominatorUnavailable; custom counts still expose their expectation-specific FailedCount while Success carries the verdict and Facts carry observed values while Name holds human-oriented display text.
func (Result) String ¶
String renders one Result as a single human-readable line prefixed with ✓ or ✗. Successful per-row results include Total only when Total is greater than zero. Tolerated results explicitly say "tolerated" and still show raw FailedCount, Total, FailedPercent, sample values, and failed keys under the existing display caps. Failing per-row results include FailedCount, FailedPercent, sample values, and failed keys. Table-level results omit row denominators; failing lines show Name only when FailedCount is zero. Sample values and failed keys are truncated to maxDisplay (10) entries with an ellipsis.
type ResultFacts ¶
type ResultFacts struct {
// ObservedCount is a table-level integer observation when set.
ObservedCount *int
// ObservedFloat is a floating-point aggregate observation when set.
ObservedFloat *float64
// ConfiguredCount is the exact-count threshold for equal-style expectations.
ConfiguredCount *int
// ConfiguredCountLower is the inclusive lower integer bound.
ConfiguredCountLower *int
// ConfiguredCountUpper is the inclusive upper integer bound.
ConfiguredCountUpper *int
// ConfiguredFloatLower is the inclusive lower floating-point bound.
ConfiguredFloatLower *float64
// ConfiguredFloatUpper is the inclusive upper floating-point bound.
ConfiguredFloatUpper *float64
// ConfiguredFloatBound is the single-sided floating-point threshold.
ConfiguredFloatBound *float64
// ConfiguredBound is the per-row comparison threshold with a driver-bound type.
ConfiguredBound any
// ConfiguredBoundLower is the inclusive per-row lower bound.
ConfiguredBoundLower any
// ConfiguredBoundUpper is the inclusive per-row upper bound.
ConfiguredBoundUpper any
// ConfiguredMaxFailedCount is the inclusive maximum failed-row bound when a
// WithMaxFailedCount policy decorated this result. Nil means no tolerance
// was applied.
ConfiguredMaxFailedCount *int
}
ResultFacts holds machine-readable observations and configured thresholds separate from display text. Threshold fields are populated by built-in expectations at construction time and must not be parsed from Name.
type RowCountBuilder ¶
type RowCountBuilder struct{}
RowCountBuilder builds table-level COUNT(*) expectations. Start with RowCount. An empty table yields count 0 (not a vacuous pass). Results set RowDenominatorUnavailable, append the observed count to Name, and store it in ResultFacts.ObservedCount.
func RowCount ¶
func RowCount() RowCountBuilder
RowCount returns a builder for table row-count checks.
func (RowCountBuilder) Between ¶
func (RowCountBuilder) Between(lo, hi int) Expectation
Between returns a table-level expectation that lo <= row count <= hi (inclusive). Results use KindRowCountBetween.
func (RowCountBuilder) Equal ¶
func (RowCountBuilder) Equal(want int) Expectation
Equal returns a table-level expectation of exactly want rows. Results use KindRowCountEqual.
func (RowCountBuilder) GreaterOrEqual ¶
func (RowCountBuilder) GreaterOrEqual(bound int) Expectation
GreaterOrEqual returns a table-level expectation that row count >= bound. Results use KindRowCountGreaterEqual.
func (RowCountBuilder) GreaterThan ¶
func (RowCountBuilder) GreaterThan(bound int) Expectation
GreaterThan returns a table-level expectation that row count > bound. Results use KindRowCountGreaterThan.
func (RowCountBuilder) LessOrEqual ¶
func (RowCountBuilder) LessOrEqual(bound int) Expectation
LessOrEqual returns a table-level expectation that row count <= bound. Results use KindRowCountLessEqual.
func (RowCountBuilder) LessThan ¶
func (RowCountBuilder) LessThan(bound int) Expectation
LessThan returns a table-level expectation that row count < bound. Results use KindRowCountLessThan.
type RowDenominator ¶
type RowDenominator string
RowDenominator reports whether Total and FailedPercent are meaningful for a result. Table-level checks use RowDenominatorUnavailable so zero totals are not confused with an empty evaluated population.
const ( // RowDenominatorAvailable marks per-row expectations where Total is the // table row count and FailedPercent is computed from FailedCount. RowDenominatorAvailable RowDenominator = "available" // and percentages are not meaningful. RowDenominatorUnavailable RowDenominator = "unavailable" )
type RowKey ¶
type RowKey []any
RowKey identifies a failing table row by caller-supplied key column values in the same order as the WithKey columns passed to ValidateTable.
type Scope ¶ added in v0.2.0
type Scope struct {
// contains filtered or unexported fields
}
Scope is an immutable scope definition containing a caller identity, a dialect-neutral predicate authored with ? placeholders, and bound values. Its fields are intentionally unexported so scope data cannot be modified after construction.
func TrustedScope ¶ added in v0.2.0
TrustedScope constructs a Scope from trusted Go-code predicate input. Validation is deferred until the scope is attached to ValidateTable.
type StringColumn ¶
type StringColumn struct {
// contains filtered or unexported fields
}
StringColumn is the entry point for string emptiness and length checks on one column. Construct one with String. Length predicates use the active Dialect.StringLength expression.
func String ¶
func String(name string) StringColumn
String returns a builder for string column checks on name.
func (StringColumn) Empty ¶
func (c StringColumn) Empty() Expectation
Empty returns a per-row expectation that the string is empty (SQL NULL fails). An empty table passes vacuously. Results use KindEmpty.
func (StringColumn) LenBetween ¶
func (c StringColumn) LenBetween(lo, hi int) Expectation
LenBetween returns a per-row expectation that lo <= dialect string-length <= hi (inclusive). SQL NULL fails. Length uses Dialect.StringLength. An empty table passes vacuously. Results use KindLenBetween.
func (StringColumn) LenEqual ¶
func (c StringColumn) LenEqual(n int) Expectation
LenEqual returns a per-row expectation that the dialect string-length of the column equals n. SQL NULL fails. Length uses Dialect.StringLength. An empty table passes vacuously. Results use KindLenEqual.
func (StringColumn) NotEmpty ¶
func (c StringColumn) NotEmpty() Expectation
NotEmpty returns a per-row expectation that the string is non-empty after trimming is not applied—only SQL NULL and the empty string fail. An empty table passes vacuously. Results use KindNotEmpty.
type Suite ¶
type Suite struct {
// contains filtered or unexported fields
}
Suite is an ordered set of SQL expectations over a database table.
Example (Basic) ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"sync"
"github.com/busyminds/gxsql"
)
const exampleDriverName = "gxsql_example_test"
var registerExampleDriverOnce sync.Once
func openExampleDB(scenario string) (*sql.DB, error) {
registerExampleDriverOnce.Do(func() {
sql.Register(exampleDriverName, exampleDriver{})
})
return sql.Open(exampleDriverName, scenario)
}
type exampleDriver struct{}
func (exampleDriver) Open(name string) (driver.Conn, error) {
return exampleConn{scenario: name}, nil
}
type exampleConn struct {
scenario string
}
func (c exampleConn) Prepare(string) (driver.Stmt, error) { return nil, driver.ErrSkip }
func (c exampleConn) Close() error { return nil }
func (c exampleConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip }
func (c exampleConn) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
switch c.scenario {
case "pass":
return c.passQuery(query, args)
case "keys":
return c.keysQuery(query, args)
case "customcount":
return c.customCountQuery(query, args)
case "error":
return nil, fmt.Errorf("example driver: query failed")
default:
return nil, fmt.Errorf("example driver: unknown scenario %q", c.scenario)
}
}
func (exampleConn) passQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(0)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func (exampleConn) customCountQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "orders" WHERE TRUE AND status = ?`:
if err := expectArgs(args, "pending"); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected custom count query")
}
}
func (exampleConn) keysQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(3)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT "id" FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ? ORDER BY "id"`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"id"}, []driver.Value{int64(2)}, []driver.Value{int64(3)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func expectArgs(args []driver.NamedValue, want ...any) error {
if len(args) != len(want) {
return fmt.Errorf("example driver: arg count = %d, want %d", len(args), len(want))
}
for i := range want {
if args[i].Value != want[i] {
return fmt.Errorf("example driver: arg %d = %v, want %v", i, args[i].Value, want[i])
}
}
return nil
}
type exampleRows struct {
cols []string
rows [][]driver.Value
idx int
}
func newExampleRows(cols []string, rows ...[]driver.Value) driver.Rows {
return &exampleRows{cols: cols, rows: rows}
}
func (r *exampleRows) Columns() []string { return r.cols }
func (r *exampleRows) Close() error { return nil }
func (r *exampleRows) Next(dest []driver.Value) error {
if r.idx >= len(r.rows) {
return io.EOF
}
copy(dest, r.rows[r.idx])
r.idx++
return nil
}
func main() {
db, err := openExampleDB("pass")
if err != nil {
panic(err)
}
defer db.Close()
suite := gxsql.NewSuite(
gxsql.RowCount().Equal(2),
gxsql.Int("age").Between(0, 120),
)
report, err := suite.ValidateTable(
context.Background(), db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.SQLite()),
)
if err != nil {
panic(err)
}
fmt.Println(report.OK())
fmt.Println(report.String())
}
Output: true gxsql report: 2/2 expectations passed ✓ row count == 2: got 2 ✓ age between [0,120] (2 rows)
Example (ContinueOnError) ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"sync"
"github.com/busyminds/gxsql"
)
const exampleDriverName = "gxsql_example_test"
var registerExampleDriverOnce sync.Once
func openExampleDB(scenario string) (*sql.DB, error) {
registerExampleDriverOnce.Do(func() {
sql.Register(exampleDriverName, exampleDriver{})
})
return sql.Open(exampleDriverName, scenario)
}
type exampleDriver struct{}
func (exampleDriver) Open(name string) (driver.Conn, error) {
return exampleConn{scenario: name}, nil
}
type exampleConn struct {
scenario string
}
func (c exampleConn) Prepare(string) (driver.Stmt, error) { return nil, driver.ErrSkip }
func (c exampleConn) Close() error { return nil }
func (c exampleConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip }
func (c exampleConn) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
switch c.scenario {
case "pass":
return c.passQuery(query, args)
case "keys":
return c.keysQuery(query, args)
case "customcount":
return c.customCountQuery(query, args)
case "error":
return nil, fmt.Errorf("example driver: query failed")
default:
return nil, fmt.Errorf("example driver: unknown scenario %q", c.scenario)
}
}
func (exampleConn) passQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(0)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func (exampleConn) customCountQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "orders" WHERE TRUE AND status = ?`:
if err := expectArgs(args, "pending"); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected custom count query")
}
}
func (exampleConn) keysQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(3)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT "id" FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ? ORDER BY "id"`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"id"}, []driver.Value{int64(2)}, []driver.Value{int64(3)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func expectArgs(args []driver.NamedValue, want ...any) error {
if len(args) != len(want) {
return fmt.Errorf("example driver: arg count = %d, want %d", len(args), len(want))
}
for i := range want {
if args[i].Value != want[i] {
return fmt.Errorf("example driver: arg %d = %v, want %v", i, args[i].Value, want[i])
}
}
return nil
}
type exampleRows struct {
cols []string
rows [][]driver.Value
idx int
}
func newExampleRows(cols []string, rows ...[]driver.Value) driver.Rows {
return &exampleRows{cols: cols, rows: rows}
}
func (r *exampleRows) Columns() []string { return r.cols }
func (r *exampleRows) Close() error { return nil }
func (r *exampleRows) Next(dest []driver.Value) error {
if r.idx >= len(r.rows) {
return io.EOF
}
copy(dest, r.rows[r.idx])
r.idx++
return nil
}
func main() {
db, err := openExampleDB("error")
if err != nil {
panic(err)
}
defer db.Close()
report, err := gxsql.NewSuite(
gxsql.Int("age").Between(0, 120),
gxsql.String("email").NotEmpty(),
).ValidateTable(
context.Background(), db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.SQLite()),
gxsql.ContinueOnError(),
)
fmt.Println(err == nil)
fmt.Println(report.Results[0].Err != nil && report.Results[1].Err != nil)
fmt.Println(report.String())
}
Output: true true gxsql report: 0/2 expectations passed ✗ age between [0,120] ✗ email not empty
Example (FailedRowKeysAndSummary) ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"sync"
"github.com/busyminds/gxsql"
)
const exampleDriverName = "gxsql_example_test"
var registerExampleDriverOnce sync.Once
func openExampleDB(scenario string) (*sql.DB, error) {
registerExampleDriverOnce.Do(func() {
sql.Register(exampleDriverName, exampleDriver{})
})
return sql.Open(exampleDriverName, scenario)
}
type exampleDriver struct{}
func (exampleDriver) Open(name string) (driver.Conn, error) {
return exampleConn{scenario: name}, nil
}
type exampleConn struct {
scenario string
}
func (c exampleConn) Prepare(string) (driver.Stmt, error) { return nil, driver.ErrSkip }
func (c exampleConn) Close() error { return nil }
func (c exampleConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip }
func (c exampleConn) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
switch c.scenario {
case "pass":
return c.passQuery(query, args)
case "keys":
return c.keysQuery(query, args)
case "customcount":
return c.customCountQuery(query, args)
case "error":
return nil, fmt.Errorf("example driver: query failed")
default:
return nil, fmt.Errorf("example driver: unknown scenario %q", c.scenario)
}
}
func (exampleConn) passQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(0)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func (exampleConn) customCountQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "orders" WHERE TRUE AND status = ?`:
if err := expectArgs(args, "pending"); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected custom count query")
}
}
func (exampleConn) keysQuery(query string, args []driver.NamedValue) (driver.Rows, error) {
switch query {
case `SELECT COUNT(*) FROM "users"`:
return newExampleRows([]string{"count"}, []driver.Value{int64(3)}), nil
case `SELECT COUNT(*) FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ?`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"count"}, []driver.Value{int64(2)}), nil
case `SELECT "id" FROM "users" WHERE "age" IS NULL OR "age" < ? OR "age" > ? ORDER BY "id"`:
if err := expectArgs(args, int64(0), int64(120)); err != nil {
return nil, err
}
return newExampleRows([]string{"id"}, []driver.Value{int64(2)}, []driver.Value{int64(3)}), nil
default:
return nil, fmt.Errorf("example driver: unexpected query %q", query)
}
}
func expectArgs(args []driver.NamedValue, want ...any) error {
if len(args) != len(want) {
return fmt.Errorf("example driver: arg count = %d, want %d", len(args), len(want))
}
for i := range want {
if args[i].Value != want[i] {
return fmt.Errorf("example driver: arg %d = %v, want %v", i, args[i].Value, want[i])
}
}
return nil
}
type exampleRows struct {
cols []string
rows [][]driver.Value
idx int
}
func newExampleRows(cols []string, rows ...[]driver.Value) driver.Rows {
return &exampleRows{cols: cols, rows: rows}
}
func (r *exampleRows) Columns() []string { return r.cols }
func (r *exampleRows) Close() error { return nil }
func (r *exampleRows) Next(dest []driver.Value) error {
if r.idx >= len(r.rows) {
return io.EOF
}
copy(dest, r.rows[r.idx])
r.idx++
return nil
}
func main() {
db, err := openExampleDB("keys")
if err != nil {
panic(err)
}
defer db.Close()
report, err := gxsql.NewSuite(gxsql.Int("age").Between(0, 120)).WithSampleCap(0).ValidateTable(
context.Background(), db, gxsql.Table("users"),
gxsql.WithDialect(gxsql.SQLite()),
gxsql.WithKey("id"),
gxsql.WithFailedKeysCap(0),
)
if err != nil {
panic(err)
}
fmt.Println(report.OK())
fmt.Println(report.String())
fmt.Println(report.Results[0].FailedKeys)
}
Output: false gxsql report: 0/1 expectations passed ✗ age between [0,120] 2/3 failed (66.7%) e.g. [] @ [[2] [3]] [[2] [3]]
func NewSuite ¶
func NewSuite(exps ...Expectation) *Suite
NewSuite builds a suite from the given expectations, evaluated in order.
func (*Suite) ValidateTable ¶
func (s *Suite) ValidateTable( ctx context.Context, db DB, table TableRef, opts ...Option, ) (Report, error)
ValidateTable runs every expectation in declaration order (collect-all, never fail-fast on policy failures) and returns the aggregated Report. It uses Postgres when WithDialect is not supplied.
Validation-policy failures are returned as (report, nil); gate with report.Err(), which yields a *ValidationError recoverable via errors.As. Run-level option errors (including invalid scope) abort with (zero Report, err) before evaluation starts. Expectation preflight failures collected before SQL starts return a zero Report and *PreflightErrors unless ContinueOnError is set, each affected slot records Result.Err. The first database, rendering, scan, or context error aborts with a zero Report and error unless ContinueOnError is set, when the error is recorded on that result and evaluation continues.
func (*Suite) WithFailedKeysCap ¶
WithFailedKeysCap sets the maximum failed-row keys retained per Result and returns the suite for chaining. Zero means unlimited. FailedCount and FailedPercent remain complete when keys are capped.
func (*Suite) WithSampleCap ¶
WithSampleCap sets the maximum sample values retained per Result and returns the suite for chaining. Zero disables sample collection.
type TableRef ¶
TableRef names a database table for Suite.ValidateTable. Schema and Name are quoted separately by the active Dialect; raw strings are never concatenated into SQL unquoted.
func SchemaTable ¶
SchemaTable returns a schema-qualified table reference. Both schema and name must satisfy identifier validation when rendered.
type ValidationError ¶
type ValidationError struct {
// Report is the complete validation outcome, including passing results.
Report Report
}
ValidationError wraps a failed Report as an error for runtime gating. Recover the full report via errors.As and the Report field.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error summarizes the number and display names of failed expectations.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package gxsqltest adapts a gxsql.Suite to Go tests.
|
Package gxsqltest adapts a gxsql.Suite to Go tests. |
|
internal
|
|
|
conformance
Package conformance contains the shared real-engine contract tests for gxsql dialects.
|
Package conformance contains the shared real-engine contract tests for gxsql dialects. |