Documentation
¶
Overview ¶
Package sqlanalysis reads out of a SQL statement the four things semantic matching compares against: the operation, the tables touched, the columns and values constrained in the WHERE clause, and the values an INSERT or UPDATE writes.
It is deliberately regex-based rather than a parser. What the EXPECT clauses assert is shallow - which table, which operation, which columns - and a full grammar per dialect would be a large dependency to answer a small question. The cost of that choice is that exotic SQL reads as no SQL rather than as wrong SQL, which fails a spec loudly instead of matching the wrong mock.
Every proxy that speaks a SQL protocol uses this package. Before it existed the MySQL and PostgreSQL proxies each carried their own copy and the two had drifted: one captured a dotted table prefix and resolved binds, the other could not and did not. Differences that are genuinely about the database live on Dialect, where they are named; anything else is one behaviour.
Index ¶
Constants ¶
const PresentSentinel = "PRESENT"
PresentSentinel is the value reported for a column constrained by a bind whose value is not available. VERIFY_WHERE compares against it when a spec cares that a column was constrained but not what it was constrained to.
Variables ¶
var ( PostgreSQL = Dialect{ Name: "postgresql", Bind: BindPositional, Quote: `"`, SchemaQualified: true, Where: ScopeWholeStatement, DedupeColumns: true, } MySQL = Dialect{ Name: "mysql", Bind: BindAnonymous, Quote: "`", SchemaQualified: false, Where: ScopeAfterWhere, DedupeColumns: false, } Oracle = Dialect{ Name: "oracle", Bind: BindNamed, Quote: `"`, SchemaQualified: true, Where: ScopeWholeStatement, DedupeColumns: true, } )
Functions ¶
func Operation ¶
Operation returns the DML verb a statement performs, or "" if it performs none. WITH reports SELECT: a common table expression is a read however much machinery precedes it.
Types ¶
type BindStyle ¶
type BindStyle int
BindStyle is how a dialect spells a placeholder in a statement.
const ( // BindPositional is PostgreSQL's $1, $2 - resolvable against an ordered // list of parameter values. BindPositional BindStyle = iota // BindAnonymous is MySQL's ?, which carries no identity, so a value can // only be resolved by counting placeholders. BindAnonymous // BindNamed is Oracle's :Name, resolvable against a map. BindNamed )
type Binds ¶
type Binds struct {
// Positional holds $1/? values in order.
Positional []string
// Named holds :Name values by name, without the leading colon.
Named map[string]string
}
Binds carries whatever parameter values the proxy managed to recover. Both fields may be empty: a dialect that cannot recover values still reports which columns were constrained, using PresentSentinel for the value.
type Dialect ¶
type Dialect struct {
Name string
// Bind is how a placeholder is spelled.
Bind BindStyle
// Quote holds the characters that may wrap an identifier, if any.
Quote string
// SchemaQualified is whether a table may be written schema.table, which
// changes what an INSERT INTO target looks like and how a table reference
// is recognised.
SchemaQualified bool
// Where is how much of the statement is scanned for conditions.
Where WhereScope
// DedupeColumns reports each constrained column once even when it appears
// in several conditions.
DedupeColumns bool
}
Dialect names the differences between databases that this package has to know about. Nothing else belongs here: a difference two proxies happen to have is drift to resolve, not a dialect.
type Result ¶
type Result struct {
Operation string
WhereColumns []string
WhereValues map[string]string
WrittenValues map[string]string
}
Result is what a statement says about itself.
type WhereScope ¶
type WhereScope int
WhereScope is how much of a statement is scanned for WHERE conditions.
const ( // ScopeWholeStatement scans everywhere, so a condition inside a subquery // or a JOIN ... ON is reported alongside the outer WHERE. Correct for a // dialect whose pagination nests the real query inside a wrapper. ScopeWholeStatement WhereScope = iota // ScopeAfterWhere scans only the text following WHERE, stopping at the // first ORDER/LIMIT/GROUP/HAVING. ScopeAfterWhere )