Documentation
¶
Overview ¶
Package query turns a validated query into the statement that answers it.
The one place in this system where SQL is not written in advance, and the reason ADR-0026 exists. The rule it works under is exact: every character of the statement is a constant of this package, chosen by a switch over a type the domain validated, and every value is bound as a parameter. No byte that arrived in a request reaches the text.
Which is why this is a package of its own rather than a file in the adapter beside it. The boundary is enforced rather than remembered - the depguard rule in .golangci.yml refuses `fmt` and the driver here, so the two ways of accidentally putting a value into a statement are not available to write in the first place - and FuzzCompile proves the result.
Index ¶
- func Key(term view.SortTerm, item work.WorkItem) string
- type Boundary
- type SearchBoundary
- type Statement
- func Count(search repository.ItemSearch) (Statement, error)
- func Groups(search repository.ItemSearch, probe int) (Statement, error)
- func Rows(search repository.ItemSearch, boundary Boundary, probe int) (Statement, error)
- func Search(search repository.TextSearch, meaning string, boundary SearchBoundary, ...) (Statement, error)
- func SearchRows(search repository.TextSearch, boundary Boundary, probe int) (Statement, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Key ¶
Key renders one sort field of one row as the text a cursor carries.
Here rather than in the adapter, next to the parsing that reads it back: the two have to agree byte for byte, and a boundary that encodes as one thing and decodes as another is a page that skips rows rather than an error anybody sees.
Types ¶
type Boundary ¶
Boundary is a decoded cursor: one key per sort term, and the identifier that breaks the tie.
The keys are text because that is what a cursor carries; the compiler turns each one back into the type its sort field holds, because a boundary compared as text against a timestamp column is a comparison the database refuses at run time.
type SearchBoundary ¶
SearchBoundary is a decoded search cursor: the rank of the last row of the previous page, and the identifier that breaks a tie between equal ranks.
type Statement ¶
Statement is a compiled query: text, and the values it is missing.
func Count ¶
func Count(search repository.ItemSearch) (Statement, error)
Count compiles the second pass an exact count costs: the same filter, without order, page or projection.
func Groups ¶
func Groups(search repository.ItemSearch, probe int) (Statement, error)
Groups compiles the board projection: the first entries of every distinct value of one field.
A window function rather than a query per group, because the number of groups is data - a board has as many columns as somebody made - and a query per column is a round trip per column. The row number is computed in the same order the entries are returned in, so a group's page is the beginning of that group in that order, and its cursor continues it.
func Rows ¶
func Rows(search repository.ItemSearch, boundary Boundary, probe int) (Statement, error)
Rows compiles an ungrouped query: one page of entries in one order.
func Search ¶
func Search( search repository.TextSearch, meaning string, boundary SearchBoundary, probe int, ) (Statement, error)
Search compiles the full text search: one page of entries in the order the database ranked them (ADR-0034).
Three things are here that no other statement in this package has.
The join to `container`, so that each row carries the hub it sits under. The search spans hubs, so the permission is asked about each row's place in the tree, and reading the collections back afterwards would be a query per collection in the page.
The rank, which is the ordering. `ts_rank_cd` over the document, taking whichever of the two tsqueries answered better - the searcher's configuration or `simple` - because a row found by one of them and not the other would otherwise rank last however well it matched. The weights the document carries do the rest: a hit in a title outranks one buried in a note (migration 0019).
And the keyset over that rank. Both keys descend, which is what lets the boundary be one row comparison rather than the three-clause form a mixed ordering needs; the identifier is a UUIDv7, so descending on it means equally-ranked entries come newest first.
func SearchRows ¶
func SearchRows(search repository.TextSearch, boundary Boundary, probe int) (Statement, error)
SearchRows compiles a search that has **no words**: a filtered read of the whole workspace, or of one container, ordered rather than ranked (ADR-0064).
The same statement `Rows` writes, with the join to `container` the search needs so that each row carries the hub the narrowing is asked about - and a constant rank, because the answer's shape is the same and nothing ranked it. Everything that decides *which* rows is the query's own: `node` writes the filter, `ordering` and `keyset` the order and the cursor. There is no tsquery, no trigram branch and no neighbourhood, because there are no words for any of them to read.