Documentation
¶
Overview ¶
Package pwdata serves the pw dev data pane from inside the application.
It lives in the application process because that is the only place the development database can be reached. An embedded SQLite connection is a process-local handle rather than an endpoint, and an in-memory one has no external existence at all, so a tool outside the process can address neither. Running here also means every read and write goes through the pool, the driver, and the diagnostics the project actually configured.
Nothing here reaches a release build. The framework starts it only under the pwdev build mode, and only when pw dev has told the process where its console is, so an application compiled by pw build links no call that starts it.
Index ¶
- Variables
- func ArgBool(name, value string) (bool, error)
- func ArgFloat32(name, value string) (float32, error)
- func ArgFloat64(name, value string) (float64, error)
- func ArgInt(name, value string) (int, error)
- func ArgInt32(name, value string) (int32, error)
- func ArgInt64(name, value string) (int64, error)
- func ArgString(_, value string) (string, error)
- func ArgTime(name, value string) (time.Time, error)
- func RegisterQuery(query Query)
- type Column
- type Connection
- func (c *Connection) Columns(ctx context.Context, table string) ([]Column, error)
- func (c *Connection) DeleteRow(ctx context.Context, edit RowEdit) (int64, error)
- func (c *Connection) Engine() string
- func (c *Connection) Exec(ctx context.Context, statement string) Result
- func (c *Connection) Explain(ctx context.Context, statement string) Result
- func (c *Connection) ExplainQuery(ctx context.Context, pkg, name string, args []string) Result
- func (c *Connection) ForeignKeys(ctx context.Context, table string) map[string]ForeignKey
- func (c *Connection) InsertRow(ctx context.Context, edit RowEdit) (int64, error)
- func (c *Connection) MigrationState(ctx context.Context) (Migration, error)
- func (c *Connection) Referenced(ctx context.Context, table, column, value string) (Page, error)
- func (c *Connection) Rows(ctx context.Context, table string, offset int) (Page, error)
- func (c *Connection) RunQuery(ctx context.Context, pkg, name string, args []string) Result
- func (c *Connection) Tables(ctx context.Context) ([]Table, error)
- func (c *Connection) UpdateRow(ctx context.Context, edit RowEdit) (int64, error)
- type ForeignKey
- type Migration
- type Page
- type Param
- type Query
- type Result
- type RowEdit
- type Server
- type Table
Constants ¶
This section is empty.
Variables ¶
var ErrUnsupportedParams = errors.New(
"this statement takes a parameter no form field can produce, so it cannot be run from here; " +
"call it from a test or a handler instead")
ErrUnsupportedParams is what a declared statement reports when a form cannot produce one of its arguments.
The statement is still listed. A developer scanning for it should find it and read why it cannot be run here, rather than conclude that generation missed it or that the statement does not exist.
var SupportedArgKinds = map[string]string{
"string": "ArgString",
"int": "ArgInt",
"int64": "ArgInt64",
"int32": "ArgInt32",
"float64": "ArgFloat64",
"float32": "ArgFloat32",
"bool": "ArgBool",
"time.Time": "ArgTime",
}
SupportedArgKinds are the parameter types a form can produce a value for. A declared statement taking anything else is registered without a form and says so, rather than being left out of the list entirely.
Functions ¶
func ArgFloat32 ¶
func ArgFloat64 ¶
func ArgString ¶
ArgString passes text through. It exists so that generated code calls a converter for every parameter rather than special-casing one.
func ArgTime ¶
ArgTime accepts the forms a browser date or datetime field produces, and RFC 3339 for a value pasted from somewhere else.
func RegisterQuery ¶
func RegisterQuery(query Query)
RegisterQuery adds a declared statement. It is called from generated code at package initialisation, so it takes no error: a duplicate is a generation defect rather than something a running pane can report.
Types ¶
type Column ¶
type Column struct {
Name string `json:"name"`
Type string `json:"type"`
NotNull bool `json:"notNull"`
// PrimaryKey is the one-based position in the primary key, or zero when the
// column is not part of one. Position matters because an edit addresses a
// row by its whole key, in order.
PrimaryKey int `json:"primaryKey"`
}
Column is one column of one table, as the engine's own catalog describes it.
type Connection ¶
type Connection struct {
// Label identifies the connection, spelled group#ordinal by the runtime.
Label string
Group string
// Driver is per connection rather than per project, so the dialect is
// resolved here. Nothing forbids two groups on two engines.
Driver string
// ReadOnly marks a replica. The pane refuses to write through one, which is
// not a policy it applies but a fact about the connection.
ReadOnly bool
// contains filtered or unexported fields
}
Connection is one configured pool the pane can address.
A project may declare several, and requirement:read-write-splitting means a group can hold replicas. The pane addresses one at a time rather than the group, because selection inside a group is round robin and a page that could not say which replica answered would be unable to show the one question replicas raise: whether this one has caught up.
func NewConnection ¶
func NewConnection(label, group, driver string, readOnly bool, db sqlbind.SQLExecutor) Connection
NewConnection describes one pool for the pane. db is a *sql.DB or the native executor of an engine that bypasses database/sql.
func (*Connection) Engine ¶
func (c *Connection) Engine() string
Engine names the dialect this connection speaks.
func (*Connection) Exec ¶
func (c *Connection) Exec(ctx context.Context, statement string) Result
Exec runs a statement the developer wrote and returns whatever it produced.
This is a development database reached from a loopback console in a build mode api:cli-build cannot emit, and a developer who can edit rows can already write any statement through the schema. Bounding what they may type would therefore buy nothing; what is bounded is the number of rows that come back.
func (*Connection) Explain ¶
func (c *Connection) Explain(ctx context.Context, statement string) Result
Explain runs the plan-only form of a statement.
The prefixes are the ones requirement:query-diagnostics already uses for a slow statement, so a plan read here and a plan attached to a data:query-record come from the same request. ANALYZE is never used: it would execute the statement a second time, which is the wrong thing to do to a write and a waste on a read.
func (*Connection) ExplainQuery ¶
ExplainQuery reads the plan of a declared statement, built with the arguments a run would use, so the plan is of the statement the application would run.
func (*Connection) ForeignKeys ¶
func (c *Connection) ForeignKeys(ctx context.Context, table string) map[string]ForeignKey
ForeignKeys reports what one table references.
A dialect with no statement for it, or a database whose catalog refuses the question, yields none rather than an error: the grid is still readable without links, and losing the whole page over a missing affordance would be the wrong trade.
func (*Connection) InsertRow ¶
InsertRow adds one row. Values not named are left to the column default, which is the difference between an insert here and one that would overwrite what the schema decided.
func (*Connection) MigrationState ¶
func (c *Connection) MigrationState(ctx context.Context) (Migration, error)
MigrationState reads the applied version.
Display only. api:cli-migrate owns applying and rolling back, and api:cli-dev already rolls back and reseeds on its own when a migration source changes — a second actor deciding the same thing from a page would be one too many.
func (*Connection) Referenced ¶
Referenced reads the rows one foreign key value points at.
The column and table come from the catalog, and the value travels as a bind parameter, so following a link is a selection rather than a query the page composed. That is what keeps this on the browsing side of the pane instead of being the filter box requirement:dev-data-pane declines to offer.
func (*Connection) Rows ¶
Rows reads one page of one table.
The table name is matched against the catalog before it reaches a statement, so what a request carries is a selection rather than SQL. Ordering is by primary key where there is one, because a page whose order changes between reads shows the same row twice and never shows another.
func (*Connection) RunQuery ¶
RunQuery executes one declared statement with the supplied arguments.
The statement text comes from the generated builder, so the pane runs what the application would run rather than an imitation of it, and a query whose SQL is assembled conditionally is assembled the same way here.
type ForeignKey ¶
type ForeignKey struct {
// Column is the column in this table.
Column string `json:"column"`
// Table and Target are what it references.
Table string `json:"table"`
Target string `json:"target"`
}
ForeignKey is one reference a table makes.
It is what turns a grid of identifiers into something navigable: a column holding 42 means nothing until the row it points at is one click away, and that click is the difference between reading data and reading a table.
type Migration ¶
type Migration struct {
// Present is false when the table is absent, which means nothing has been
// applied here rather than that something went wrong.
Present bool
Version int64
Applied int
}
Migration is what the connected database says about its own schema version.
type Page ¶
type Page struct {
Table string `json:"table"`
Columns []Column `json:"columns"`
Rows [][]*string `json:"rows"`
Offset int `json:"offset"`
Limit int `json:"limit"`
// Ordered reports whether the page has a stable order. Without a primary
// key it does not, and saying so beats implying a stability the engine is
// not promising.
Ordered bool `json:"ordered"`
More bool `json:"more"`
}
Page is one bounded read of one table.
type Param ¶
type Param struct {
Name string `json:"name"`
// Kind is the Go type as written in the generated signature, which is what
// the form has to be able to produce.
Kind string `json:"kind"`
}
Param is one parameter of a declared query, as its generated builder declares it.
type Query ¶
type Query struct {
Package string `json:"package"`
Name string `json:"name"`
Exported bool `json:"exported"`
Params []Param `json:"params"`
// Build turns the supplied arguments into the statement the application
// itself would run. It is the generated builder, so what the pane executes
// is the project's own SQL rather than a second rendering of it.
Build func(args []string) (sqlbind.Statement, error) `json:"-"`
}
Query is one declared statement, registered from inside the package the generator emitted it into.
Registration happens there because a generated builder named for an unexported statement is unreachable from anywhere else, and the statements a developer most wants to try are not always the exported ones.
type Result ¶
type Result struct {
Columns []string `json:"columns"`
Rows [][]*string `json:"rows"`
// Affected is set for a statement that changed rows rather than returning
// them. Negative means the driver would not say.
Affected int64 `json:"affected"`
Returned bool `json:"returned"`
SQL string `json:"sql"`
Error string `json:"error,omitempty"`
// Truncated reports that the result had more rows than the pane will show.
Truncated bool `json:"truncated"`
}
Result is what a statement produced, whether it returned rows or a count.
type RowEdit ¶
type RowEdit struct {
Table string `json:"table"`
// Key maps primary key column names to their current values. It is empty
// for an insert.
Key map[string]string `json:"key"`
// Values maps column names to their new values. A column absent here is
// left alone on an update.
Values map[string]string `json:"values"`
// Nulls names columns to set to NULL, which an empty string cannot express.
Nulls []string `json:"nulls"`
}
RowEdit addresses one row and says what to do with it.
The key is the whole primary key, because that is what addresses exactly one row; a table without one cannot be edited here and says so. Values are bind parameters throughout — only column names reach the statement text, and only after the catalog has confirmed them.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the pane over one or more connections.
func New ¶
func New(connections []Connection, environment string) *Server
New builds a server over the connections the application opened.
The order is preserved, because it is the order the project declared and a developer reads the list expecting to recognise it.
func NewSingle ¶
func NewSingle(db sqlbind.SQLExecutor, driver, environment string) *Server
NewSingle is the ordinary case: a configuration that declares one database and never names a group.
func (*Server) Connections ¶
func (s *Server) Connections() []Connection
Connections lists what the pane can address.
func (*Server) Default ¶
func (s *Server) Default() *Connection
Default is the connection a page selects when none was asked for.
A writable one, because the pane edits and a page that opened on a replica would refuse the first edit attempted on it for a reason the developer did not choose. Falling back to the first connection keeps a project whose connections are all replicas usable for reading.
func (*Server) Handler ¶
Handler serves the data pane: schema, rows, edits, a statement console, and the declared queries the project generated.
It is mounted by the framework on a loopback listener of its own, never on the application's, so nothing here is reachable from the port the application serves.
func (*Server) Lookup ¶
func (s *Server) Lookup(label string) *Connection
Lookup finds a connection by label, or returns the default when the label is empty or unknown.
type Table ¶
type Table struct {
Name string `json:"name"`
// Framework marks a table the framework owns rather than the application.
// It is shown and readable — a developer looking at their own development
// database is not the exposure policy:query-log-safety bounds — but it is
// marked, because a row in one of these was written by code the developer
// did not write and is not theirs to reason about.
Framework bool `json:"framework"`
}
Table is one table and what the pane knows about it before reading any row.