Help us shape the future of Go! Take the 2026 Go Developer Survey

dialect

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package dialect is the part of building a model that differs per warehouse.

ONE INTERFACE WITH A CONFORMANCE SUITE, written while there was a single implementation. That order is the whole point: a second dialect added against an interface nobody tests agrees with the first wherever the author happened to look and differs everywhere else, and the difference is found by a consumer. Here it is found by `go test`.

What is NOT in here is as deliberate as what is. There is no "quote an identifier", because this package refuses a name that would need quoting instead -- see the Postgres dialect's Build for why. There is no "begin a transaction", because BigQuery does not put DDL in one and an interface that lies about that would be worse than two honest orderings.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewRows added in v0.2.0

func NewRows(m model.Model, body string) string

NewRows wraps a model's SQL in the filter that makes a build incremental.

THE WATERMARK IS A SUBQUERY AND NEVER A VALUE, which is the single most load-bearing line in this file. BigQuery hands its own TIMESTAMP back over the REST API as `"1767484800.0"` -- epoch seconds, as a JSON string -- and feeding that back in is refused outright: `Could not cast literal "1767484800.0" to type TIMESTAMP`, measured 2026-10-08. pgx, for the same column type, returns a time.Time. So a design that read MAX(w) in Go and formatted it into the next statement would be two different bugs in two warehouses, and the quiet one is the dangerous one.

AN EMPTY TARGET IS THE OTHER TRAP. MAX over no rows is NULL, and `w > NULL` is NULL, which admits nothing -- so a model whose first build landed an empty table would stay empty forever while every build reported success. The IS NULL arm is what stops that, and the conformance suite has a case for it because nothing else would ever notice.

Types

type Arg added in v0.5.0

type Arg struct {
	Name  string
	Value any
}

Arg is one bound value: its NAME for a dialect that binds by name (BigQuery's `@x`), its POSITION in Request.Args for one that binds by number (Postgres's `$n`). Both are carried so neither dialect has to guess the other's.

Value is one of string, int64, bool or Date -- the four a parameter can be typed as, and nothing a dialect has to learn to send beyond them.

type ArgPlanner added in v0.5.0

type ArgPlanner interface {
	PlanArgs(ctx context.Context, statement string, args []Arg) (Plan, error)
}

ArgPlanner is a Planner for a statement with bound arguments.

SEPARATE FROM Planner and not a parameter on it: Planner's callers -- `serve`, the build -- price statements somebody typed, which have no arguments, and widening its signature would touch each of them to pass nothing. A statement naming `@x` cannot be priced without `x`, so the one caller that binds is the one that asks for this.

type Binder added in v0.5.0

type Binder interface {
	// Placeholder is what stands for the argument at `position` (1-based in
	// Request.Args) named `name`. A dialect uses whichever of the two it
	// binds by and ignores the other.
	Placeholder(position int, name string) string
}

Binder is implemented by a dialect that can say how a query names an argument.

ON THE DIALECT AND NOT THE CONNECTION: a placeholder is a fact about the SQL, and whoever writes the SQL -- the API's renderer -- has a dialect before it has a connection.

type Column added in v0.3.0

type Column struct {
	Name string
	Type string
}

Column is one field of a relation, as the warehouse names it.

type Conn

type Conn interface {
	// Exec runs one statement. DDL, so nothing is returned.
	Exec(ctx context.Context, statement string) error

	// Scalar runs a query of one row and one column. A query matching no row
	// returns nil, nil -- that is "nothing is there", which is an ANSWER on
	// this path and not a failure.
	Scalar(ctx context.Context, query string) (any, error)

	// Target names a relation the way the CATALOG does:
	// `bigquery://project/dataset/table`, `postgres://database/schema/table`.
	//
	// ON Conn AND NOT ON Dialect, because only a live connection knows the
	// first segment: the project for BigQuery, the database for Postgres,
	// and the latter is inside a DSN this package never sees in one piece.
	//
	// It is what puts a model on `/data`. The engine takes it on a `landed`
	// line and refuses anything that does not match its own rule -- counting
	// the refusal rather than failing the step, deliberately, because a
	// target the engine repaired would be a target the engine inferred. A
	// wrong one here is therefore a model that builds, reports success and
	// never appears, which is why the conformance suite asserts its shape.
	Target(ref string) string

	Close(ctx context.Context) error
}

Conn is the little a warehouse has to do.

Two methods, and they are the two the build loop needs: run a statement, and read one value back. A driver's own richer surface stays behind its dialect, where it belongs -- the loop must not learn what a pgx.Rows is.

type Date added in v0.5.0

type Date struct {
	Year  int
	Month time.Month
	Day   int
}

Date is a calendar day, with no time and no zone.

NOT A time.Time, which is an INSTANT: sent as one, Postgres compares a timestamptz in the session's zone and BigQuery refuses a TIMESTAMP against a DATE column. A day somebody asked for in a query string is a day.

func (Date) String added in v0.5.0

func (d Date) String() string

func (Date) Value added in v0.5.0

func (d Date) Value() (driver.Value, error)

Value makes a Date an argument database/sql-shaped drivers understand: pgx sends the text, and Postgres reads it as the type the placeholder's context says -- a DATE beside a DATE column.

type Describer added in v0.3.0

type Describer interface {
	// Columns names what a SELECT could ask for from this relation, in the
	// order the warehouse holds them.
	//
	// It costs money. The caller holds the answer; this does not cache.
	Columns(ctx context.Context, r Relation) ([]Column, error)
}

Describer is implemented by a connection that can say what ONE relation holds.

SEPARATE FROM LISTER, AND LAZY ON PURPOSE. Lister answers for the whole connection in one call because that is what BigQuery's 10 MiB floor makes cheapest; columns cannot work that way -- a project-wide COLUMNS query returns every field of every table, which is the one metadata query that is genuinely large. CHECKPOINT D put this at the relation: "columns stay lazy, per relation, because that is the level a preview already discloses and the one nobody expands by accident".

NAMES AND TYPES AND NEVER VALUES. Sampling is what Preview is for, and Preview is bounded.

type Dialect

type Dialect interface {
	// Name is what `--dialect` is given, and it is also the name the
	// reference extractor is given: a project cannot read its SQL as one
	// dialect and build it as another.
	Name() string

	// Open connects. The DSN comes from an environment variable named in the
	// command line, never from a file -- a connection string carries a
	// password and a project directory is in git.
	Open(ctx context.Context, dsn string) (Conn, error)

	// EnsureSchema is run before the models of that schema, and has to be
	// safe to run when the schema is already there.
	EnsureSchema(schema string) []string

	// KindOf is a query whose single value is "table", "view" or NULL.
	KindOf(ref string) string

	// Literal is a Go string as this warehouse's SQL spells it, quotes
	// included.
	//
	// ON THE INTERFACE BECAUSE IT MEASURABLY DIFFERS, and for no other
	// reason -- with one backslash in the statement, on 2026-10-08:
	//
	//	postgres   SELECT LENGTH('a\bc')  ->  4   a backslash is a character
	//	bigquery   SELECT LENGTH('a\bc')  ->  3   \b is a backspace
	//
	// A value a consumer wrote in an `accepted_values` test therefore means
	// two different things depending on where the project runs, and the
	// check built from it would pass against data it should refuse.
	Literal(s string) string

	// ColumnsOf is a query whose single value is the relation's column
	// names, in ordinal order, comma-separated.
	//
	// ONE VALUE AND NOT A ROW SET, so Conn keeps its two methods. `MERGE ...
	// WHEN MATCHED THEN UPDATE SET` has to name every column -- BigQuery has
	// no `SET *`, measured 2026-10-08 -- so the list has to be asked for, and
	// `STRING_AGG(column_name, ',' ORDER BY ordinal_position)` is what both
	// warehouses answer it with. A third Conn method for one caller would
	// have been the thing this package's own comment argues against.
	ColumnsOf(ref string) string

	// Build is every statement that turns the model into what its header
	// says, in order, given what is in the warehouse NOW.
	Build(m model.Model, st State) ([]string, error)
}

Dialect is one warehouse's half of building a model.

type Disposable added in v0.2.0

type Disposable interface {
	// ExpireSchema is the statement that makes everything in this schema
	// expire. Run by a test right after EnsureSchema, and by nothing else.
	ExpireSchema(schema string) string
}

Disposable is implemented by a dialect whose warehouse can be told to throw away what a TEST leaves behind.

NOT ON Dialect, and that separation is the whole point. EnsureSchema is production -- run.go calls it for every schema a consumer's models land in -- so an expiry reachable from there would be a build that quietly deletes a customer's tables in a day. There is no sentence that makes that acceptable, so the capability lives where the production path cannot reach it by accident.

It exists because a test CAN leak. The conformance suite and the runner's incremental test each create a dataset of their own, beside the client's real ones, and drop it in t.Cleanup -- which does not run after a panic, a SIGKILL or a laptop that sleeps. BigQuery has no dataset expiry at all, only `defaultTableExpirationMs` for the tables inside, so what this buys is a leak that EMPTIES rather than a leak that disappears. That residual is real and is the reason this is one small interface and not a cleanup framework.

Postgres does not implement it: its test warehouse is a container, and `docker compose down` is the expiry.

type Estimator added in v0.3.0

type Estimator interface {
	// Estimate returns the bytes the query would process.
	//
	// AN ERROR AND NEVER A ZERO when the price cannot be read. Zero is under
	// every ceiling, so a silent fallback would wave through exactly the
	// queries nobody could measure.
	Estimate(ctx context.Context, query string) (int64, error)
}

Estimator is implemented by a connection that can price a query WITHOUT RUNNING IT.

Separate from Reader, and not a method on it, for the reason Reader is separate from Conn: Postgres has no such thing to offer. A warehouse that charges for a scan can answer this, one that charges for a machine by the hour cannot, and an interface that pretended otherwise would make every Postgres caller handle an answer that is always a guess.

A refusal built on this runs BEFORE the money is spent, which is the only moment at which refusing is worth anything.

type Kind

type Kind string

Kind is what a relation IS in the warehouse right now.

Build is given it, because "create or replace" is three different statements depending on the answer and every dialect has to ask.

const (
	// Absent is nothing of that name.
	Absent Kind = ""
	Table  Kind = "table"
	View   Kind = "view"
)

func KindFrom

func KindFrom(v any) Kind

KindFrom reads what Scalar returned from a KindOf query.

Here rather than in each dialect because the three answers are the same three everywhere, and a driver that hands back []byte where another hands back string is not a dialect difference -- it is a driver difference, and turning it into a Kind twice is how the two drift.

type Lister added in v0.3.0

type Lister interface {
	// Relations lists everything in this connection a SELECT could name.
	//
	// It costs money. The caller holds the answer; this does not cache.
	Relations(ctx context.Context) ([]Relation, error)
}

Lister is implemented by a connection that can say what a SELECT could name, WITHOUT being told.

Separate from Reader for Reader's reason one level up: it is a different capability and a dialect may have one and not the other. It is also a different KIND of question -- everything else here is asked ABOUT a relation somebody already named.

ONE CALL FOR THE WHOLE CONNECTION, and that is the interface rather than an implementation detail. Measured against BigQuery on 2026-10-09: every INFORMATION_SCHEMA query is billed at a 10 MiB floor, whatever comes back. A tree that asked per node would spend 10 MB on every click -- fifty schemas is half a gigabyte, and the screen would look like it was doing nothing. The whole project costs the same as one dataset, so the whole project is what this returns.

type Merge added in v0.2.0

type Merge struct {
	// On is `t.k = s.k AND t.j = s.j`.
	On string
	// Set is `c = s.c, d = s.d`, the columns that are NOT keys.
	//
	// Empty when every column is a key. A MERGE then has nothing to update
	// and the WHEN MATCHED clause is omitted entirely -- `SET` with no
	// assignment is a syntax error in both warehouses, and "the row is
	// already exactly this row" is not a failure.
	Set string
	// Columns and Values are the INSERT's two halves, in the target's own
	// ordinal order.
	Columns string
	Values  string
}

Merge is the parts of a MERGE that are the same in every warehouse.

HERE AND NOT IN EACH DIALECT, because none of it is a dialect difference: a join on the key columns, a SET of the ones that are not keys, and an INSERT naming the target's columns. What DOES differ -- how the source is deduplicated -- is not in here, and that is the whole split.

func MergeOf added in v0.2.0

func MergeOf(m model.Model, columns []string) Merge

MergeOf builds those parts from the model's keys and the target's columns.

type Plan added in v0.4.0

type Plan struct {
	// Bytes is what the statement would scan. Zero when this warehouse does
	// not price a scan, which is every warehouse billed by the hour.
	Bytes int64

	// Kind is the warehouse's OWN name for the statement. Measured against
	// real BigQuery on 2026-10-10: SELECT, CREATE_TABLE, CREATE_VIEW,
	// INSERT, UPDATE, DELETE, MERGE, TRUNCATE_TABLE, EXPORT_DATA, SCRIPT.
	//
	// CARRIED SO A REFUSAL CAN QUOTE AND NEVER PARAPHRASE, and never parsed
	// by a caller: the day a warehouse adds a kind, a service reading this
	// as a list would classify it by not recognising it.
	Kind string

	// Reads is the WAREHOUSE's verdict that this statement only reads. It is
	// the whole reason this type exists beside Estimator.
	Reads bool
}

Plan is what a warehouse says about a statement BEFORE it runs.

type Planner added in v0.4.0

type Planner interface {
	// Plan asks the warehouse what the statement would cost and what it is,
	// WITHOUT RUNNING IT.
	//
	// AN ERROR AND NEVER A FALSE. A verdict this could not obtain is not
	// "it reads": a caller refusing on `!Reads` would be waved through by a
	// transport failure, which is the one way this check can be worse than
	// no check.
	Plan(ctx context.Context, statement string) (Plan, error)
}

Planner is implemented by a connection whose warehouse can say, before a statement runs, both what it would cost AND WHAT IT IS.

IT IS THE SECOND ANSWER TO WHAT THE CLASSIFIER CANNOT DO. `ReadOnlyGuard` is the first: Postgres can be told to refuse writes and then refuses them itself. BigQuery has no read-only session, and it has something else -- it classifies every statement server-side before running it, and says so on the dry run this code already makes.

ONE CALL AND NOT TWO. The price and the verdict come out of the same dry run, because asking twice would double the round trips on every query to learn two halves of one answer. That is also why this is not a second method on Estimator: a warehouse either answers both or answers neither.

MEASURED, AGAINST THE REAL API. `statementType` is on the dry run's response to `/queries` -- a SELECT answers SELECT, a CREATE TABLE answers CREATE_TABLE, a procedure CALL and anything multi-statement answer SCRIPT, and an EXPORT DATA that writes to a bucket answers EXPORT_DATA rather than hiding behind the SELECT it ends with.

type ReadOnlyGuard added in v0.4.0

type ReadOnlyGuard interface {
	// ReadOnly runs fn with the warehouse refusing any write for its
	// duration. Reads issued on this connection inside fn are covered.
	ReadOnly(ctx context.Context, fn func(context.Context) error) error
}

ReadOnlyGuard is implemented by a connection that can make the WAREHOUSE refuse a write, instead of this service guessing from the text.

IT IS THE ANSWER TO WHAT THE CLASSIFIER SAYS IT CANNOT DO. `serve`'s ReadOnly reads a statement over a tokeniser and its own comment names the hole: "a function call writes whatever the function writes. `SELECT nextval('s')` advances a sequence and `SELECT dblink_exec(...)` runs anything at all, and no classifier that is not a planner can see inside them. THE READ-ONLY ROLE IS THE DEFENCE THERE, not this."

A read-only role is a SECOND service account, which is a thing a client has to create, keep and rotate for a console. This is the same defence held by the warehouse and not by an IAM policy: on Postgres a transaction opened READ ONLY, where `nextval()` comes back refused by the server itself.

WHAT IT DOES NOT CLAIM: it is not a sandbox. It stops the writes the warehouse knows how to stop, which on Postgres is every ordinary one and anything a plain function reaches. A dialect that cannot offer this gets NO relaxation anywhere -- a writable credential stays refused there exactly as before, because a guarantee one warehouse can make is not a guarantee another one inherits.

type Reader added in v0.3.0

type Reader interface {
	// Read runs a query and returns at most Request.Limit rows.
	Read(ctx context.Context, req Request) (Result, error)
}

Reader is implemented by a connection that can return a result set.

NOT ON Conn, for Disposable's reason one level over. `Conn` has Exec and Scalar -- "the two the build loop needs" -- and a build never reads a result set. Keeping this off the production interface means the build loop cannot reach it, and a dialect that cannot read is refused by the one caller that wants to rather than discovered at runtime.

ON Conn's CONCRETE TYPE and not on Dialect: reading needs the connection, where ExpireSchema only needed a statement.

type Relation added in v0.3.0

type Relation struct {
	Schema string
	Name   string
}

Relation is one thing a SELECT could name.

type Request added in v0.3.0

type Request struct {
	// Query is run exactly as written. Nothing wraps it: a wrapper changes
	// what the warehouse plans, and it would be this package quietly editing
	// a statement somebody is about to be charged for.
	Query string

	// Limit is the most rows to return.
	//
	// THE LIMIT CUTS, it does not refuse. A preview that errored because a
	// table is large would be a preview that never works on the tables
	// somebody actually has. What it must not do is cut in silence, which is
	// what Result.Truncated is for.
	Limit int

	// MaxBytes is the most the warehouse may BILL for this query. Zero is no
	// bound, and a dialect that cannot enforce one ignores it.
	//
	// IT IS NOT THE SAME LIMIT AS Limit, and the difference is the whole
	// reason it exists: BigQuery bills for bytes SCANNED, and a LIMIT does
	// not reduce them -- `SELECT * FROM t LIMIT 10` over a petabyte reads
	// the petabyte. A row ceiling bounds the screen and bounds nothing else.
	MaxBytes int64

	// Args are the values the query's placeholders stand for, in the order
	// their placeholders were numbered. See Binder.
	//
	// THE ONLY WAY A CALLER'S VALUE REACHES THE WAREHOUSE, and the reason is
	// the whole of `brevis-sql-api`'s safety: a value sent as an argument is
	// data whatever it contains, and one spliced into Query is SQL. Empty for
	// every statement somebody typed, which is every caller before the API.
	Args []Arg
}

Request is one read, and everything that bounds it.

A STRUCT AND NOT THREE ARGUMENTS, because the two bounds are different KINDS of bound and the call site has to say which it means. Rows are a screen and bytes are money; a reader passing them positionally would eventually pass them the wrong way round, and one of those mistakes is expensive.

type Result added in v0.3.0

type Result struct {
	// Columns are the names, in the order the query returned them.
	Columns []string

	// Rows are the values, one slice per row, aligned with Columns.
	Rows [][]any

	// Scanned is what the warehouse says it processed, in bytes. Zero when
	// it does not say, which is every warehouse that does not charge for a
	// scan -- so it is reported and never assumed.
	//
	// IT IS THE MEASURED FIGURE AND NOT THE ESTIMATE. A gap between the two
	// is how somebody finds out that the price a refusal was built on was
	// not the price that was paid.
	Scanned int64

	// Changed is how many rows a statement that WRITES changed, and Counted
	// says whether the warehouse reported a count at all.
	//
	// TWO FIELDS AND NOT ONE, because "0 rows changed" and "this changes no
	// rows" are different answers. An UPDATE whose WHERE matched nothing is
	// the first; a CREATE TABLE is the second. A screen that said "0 rows"
	// for a table that was just created would read as a failure, and the
	// person who ran it would run it again.
	//
	// Both are zero on a read, which is every statement this service ran
	// before a connection could be unlocked for writes.
	Changed int64
	Counted bool

	// Truncated says the limit cut the answer.
	//
	// IT HAS TO BE SAID. A grid showing a thousand rows of a million, in
	// silence, is a grid that lies: somebody reads a MAX off it and is wrong,
	// and nothing on the screen told them not to.
	Truncated bool
}

Result is a result set, as a screen needs it.

`[][]any` AND NOT `[][]string`, because NULL is not the empty string. One is "nothing was recorded" and the other is a value somebody wrote; they render differently and are read differently, so they have to arrive differently. A nil cell is NULL.

type State added in v0.2.0

type State struct {
	// Current is what the relation IS right now.
	Current Kind

	// FullRefresh is `--full-refresh`: build it as if nothing were there.
	FullRefresh bool

	// Columns is the TARGET's column names in ordinal order, from ColumnsOf.
	//
	// Read only when it is going to be used -- an incremental model that is
	// adding to an existing table -- and nil otherwise. A list read on every
	// build would be a query per model for an answer three materialisations
	// out of four never look at.
	Columns []string
}

State is what the warehouse holds for this model right now, and what the caller asked for.

A STRUCT AND NOT A THIRD ARGUMENT, which is the precedent #42 set for CheckRow: the questions a build has to answer grow, and a signature that grows with them is one every implementation and every test has to be edited for, one argument at a time, with the compiler unable to tell a swapped pair apart.

func StateOf added in v0.2.0

func StateOf(ctx context.Context, d Dialect, conn Conn, ref string,
	incremental, fullRefresh bool) (State, error)

StateOf asks the warehouse the questions a build needs answered.

ONE PLACE, because two would drift and the drift would be invisible: the conformance harness mimics the build loop on purpose -- "every test here also exercises KindOf" -- and a harness that assembled the State its own way would pass a suite about itself. Both call this.

THE COLUMNS ARE READ ONLY WHEN THEY WILL BE USED. Three materialisations out of four never look at them, and a query per model for an answer nobody reads is a round trip per model per build.

func (State) Rebuild added in v0.2.0

func (s State) Rebuild() bool

Rebuild says this build creates the relation rather than adding to it.

DERIVED AND NEVER STORED. Three inputs decide it, and a fourth field holding the answer would be a second place for it to be wrong -- which is exactly how a `--full-refresh` that set the flag and forgot the field would merge into the table it was told to replace.

type WriteProbe added in v0.3.0

type WriteProbe interface {
	// CanWrite reports whether this credential could create a table in the
	// given schema. IT MUST NOT CREATE ONE.
	//
	// TRUE IS THE ONLY ANSWER A CALLER MAY TRUST. "Permission denied" is a
	// no, and so is "that dataset does not exist" -- the two arrive the same
	// way, so a caller refuses on true rather than allowing on false, and a
	// probe that could not run refuses nothing.
	CanWrite(ctx context.Context, schema string) (bool, error)
}

WriteProbe is implemented by a connection that can answer, WITHOUT WRITING, whether its own credential is allowed to.

WHY IT EXISTS AT ALL. A classifier is the only thing between a browser and a write on the read-only surface, and the classifier says in its own comment what it cannot do: a function call writes whatever the function writes, and `SELECT nextval('s')` is beyond anything that is not a planner. The read-only ROLE is the defence there -- and a role nobody checked is a role nobody has. This is what turns "we gave it a read-only service account" from a claim into a measurement.

It costs one round trip per connection, and nothing in money: on BigQuery it is a dry run, which creates nothing and bills nothing. Measured against the real API on 2026-10-09, both directions:

CREATE TABLE on a dataset the credential cannot write
  -> Access Denied: Permission bigquery.tables.create denied
CREATE TABLE on one it can
  -> validated, 0 bytes, and `bq ls` shows nothing was created

type Writer added in v0.4.0

type Writer interface {
	// Write runs a statement that changes data. The Result carries no rows
	// and no columns: Changed and Counted are the answer.
	Write(ctx context.Context, statement string) (Result, error)
}

Writer is implemented by a connection that can run a statement that CHANGES data and say how much it changed.

SEPARATE FROM Reader, for Reader's own reason: it is a different capability and a dialect may have one and not the other. It is also a different QUESTION -- a reader asks for rows and gets a grid; this one asks for an effect and gets a number, and a grid with no columns is not an answer to it.

AND SEPARATE FROM Conn.Exec, WHICH ALREADY RUNS A STATEMENT. `Exec` returns an error and nothing else -- "DDL, so nothing is returned" -- and the build loop wants exactly that. Changing its signature would touch ten call sites and the conformance suite to carry a count the build never reads. This is the eighth optional interface for the same reason there are seven others.

IT IS REACHED ONLY ON A CONNECTION SOMEBODY UNLOCKED. `serve` refuses a write everywhere else, so a dialect implementing this does not open a path; it answers one that was already open with something better than silence.

Directories

Path Synopsis
Package bigquery builds models on Google BigQuery.
Package bigquery builds models on Google BigQuery.
Package dialecttest is the suite every dialect has to pass.
Package dialecttest is the suite every dialect has to pass.
leakprobe command
Command leakprobe is the other half of `scripts/leak-check.sh`: a harness that never cleans up, and the questions you ask BigQuery afterwards.
Command leakprobe is the other half of `scripts/leak-check.sh`: a harness that never cleans up, and the questions you ask BigQuery afterwards.
Package postgres builds models on PostgreSQL.
Package postgres builds models on PostgreSQL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL