Documentation
¶
Overview ¶
Package filtertest is the filter conformance fixture: one entity with a field of every filterable type, its rows, and the rows every backend must return for each filter. SQL dialects and in-memory evaluators run the same cases through Run, so an operator means one thing everywhere .
Index ¶
- Variables
- func ArraySchema() (*filter.ResourceSchema, error)
- func DecimalSchema() (*filter.ResourceSchema, error)
- func Lookups() filter.MembershipLookup
- func NumberKeySchema() (*filter.ResourceSchema, error)
- func Parser(t testing.TB) *filter.FilterParser
- func PathParser(schema *filter.ResourceSchema) *filter.FilterParser
- func PathSchema() (*filter.ResourceSchema, error)
- func Run(t *testing.T, name string, backend Backend)
- func RunArrays(t *testing.T, schema *filter.ResourceSchema, backend Backend)
- func RunDecimals(t *testing.T, schema *filter.ResourceSchema, backend Backend)
- func RunMembership(t *testing.T, backend Backend)
- func RunPathSets(t *testing.T, schema *filter.ResourceSchema, backend Backend)
- func RunPaths(t *testing.T, schema *filter.ResourceSchema, backend Backend)
- func RunSets(t *testing.T, name string, backend Backend)
- func Schema() (*filter.ResourceSchema, error)
- func SetConfig() *core.ParserConfig
- type ArrayRecord
- type Backend
- type Case
- type DecimalRecord
- type FacetOrder
- type LookupColumn
- type LookupTable
- type MembershipCase
- type NumberKeyRecord
- type PathRecord
- type Record
- type Settings
- type Window
Constants ¶
This section is empty.
Variables ¶
var ( Base = time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) Now = func() time.Time { return Base.Add(3 * time.Second) } )
Base is the instant timestamps are offset from; Now is the parser clock relative tokens resolve against.
var ArrayFacetOrders = map[string]FacetOrder{ "scores": {Limit: 3, Keys: []string{"9", "1", "3"}}, }
ArrayFacetOrders are ArrayFacets' numeric terms facets cut through a tie, which by text would keep 100 before 3.
var ArrayFacets = map[string]map[string]int64{
"doc.tags": {"a": 2, "b": 2, "c": 1, "A": 1},
"doc.flags": {"true": 2, "false": 1},
"labels": {"x": 2, "y": 2, "a": 2, "z": 2},
"scores": {"1": 1, "3": 1, "5": 1, "7": 1, "9": 2, "20": 1, "100": 1},
}
ArrayFacets are the buckets array facets count over ArrayRecords: each row once per distinct element of the array's type.
var DecimalFacetOrders = map[string]FacetOrder{ "doc.price": {Limit: 4, Keys: []string{"10.5", "0", "9007199254740992.5", "9007199254740993.5"}}, }
DecimalFacetOrders are DecimalFacets' terms facets cut through a tie, which by text would keep 12345678901234567890 before 9007199254740992.5.
var DecimalFacets = map[string]map[string]int64{
"amount": {"0-1": 1, "1-100": 2, "100+": 7},
"doc.price": {
"0": 1, "10.5": 2, "9007199254740992.5": 1, "9007199254740993.5": 1, "9223372036854775807": 1, "9223372036854775808": 1,
"12345678901234567890": 1, "12345678901234567890.123456789012345678": 1, "12345678901234567890.123456789012345679": 1,
},
}
DecimalFacets are the buckets decimal facets count over DecimalRecords: amount's ranges and the price member's terms, each keyed by canonical text, so h's and n's equal prices share a bucket.
var DecimalOrders = map[string]string{
"amount": "jkimhnabcdefgl",
"-amount": "lgfedcbanhmikj",
"doc.price": "ihnabcdefgjklm",
"-doc.price": "mlkjgfedcbanhi",
}
DecimalOrders are the presentation orders decimal sorts give DecimalRecords: NULL greatest, ties on id in the primary direction.
var NumberKeyFacets = map[string]map[string]int64{
"attrs.n": {"7": 3, "3.5": 1, "0": 1},
"attrs.r": {"1.5": 3, "2": 2},
"attrs.ns": {"7": 2, "1": 1, "0": 1},
"f64": {"0.30000000000000004": 1, "1000000000000000000000": 1, "0": 3, "0.0000001": 1, "2.5": 1},
"f32": {"0.1": 1, "2": 2, "0.00000015": 1, "16777216": 1, "0.5": 1, "0": 1},
"i": {"7": 2, "0": 2, "9007199254740993": 1, "-1": 1, "1": 1},
"ratios": {"0.1": 1, "2": 2, "16777216": 1},
}
NumberKeyFacets are the buckets every facet of NumberKeyRecords counts, keyed by canonical decimal text: one bucket per value however it is written, a list's element once per row.
var PathFacetOrders = map[string]FacetOrder{ "attrs.price": {Limit: 3, Keys: []string{"-1", "9.5", "10.25"}}, }
PathFacetOrders are PathFacets' numeric terms facets cut through a tie, which by text would keep 10.25 and 100 before 9.5.
var PathFacets = map[string]map[string]int64{
"attrs.color": {"red": 1, "blue": 1, "Red": 1},
"attrs.on": {"true": 2, "false": 1},
"settings.mode": {"auto": 2, "manual": 1, "": 1},
"settings.level": {"0-5": 2, "5+": 2},
"attrs.price": {"10.25": 1, "9.5": 1, "-1": 1, "100": 1},
}
PathFacets are the buckets path facets count over PathRecords; an absent member is in no bucket.
var PathOrders = map[string]string{
"attrs.size": "agbecdfh",
"-attrs.size": "hfdcebga",
"attrs.seen": "aebcdfgh",
"-attrs.seen": "hgfdcbea",
"attrs.color": "gbacdefh",
"settings.level": "eabgcdfh",
"-settings.level": "hfdcgbae",
"attrs.price": "ebagcdfh",
"-attrs.price": "hfdcgabe",
}
PathOrders are the presentation orders path sorts give PathRecords: an absent member sorts as NULL (greatest), ties on id in the primary direction.
var SetSizes = []int{0, 1, 257, 100_000}
SetSizes are the `$in` / `$nin` set sizes the set cases generate: empty, one member, past PostgreSQL's default containment bound (filter.DefaultContainmentMaxValues), and an authorized-ID set.
Functions ¶
func ArraySchema ¶ added in v0.12.0
func ArraySchema() (*filter.ResourceSchema, error)
ArraySchema returns ArrayRecord's generated filter schema.
func DecimalSchema ¶ added in v0.12.0
func DecimalSchema() (*filter.ResourceSchema, error)
DecimalSchema returns DecimalRecord's generated filter schema.
func Lookups ¶
func Lookups() filter.MembershipLookup
Lookups resolves the cases' memberships over LookupTables with filter.MatchLookupRow: the in-memory answer to every lookup.
func NumberKeySchema ¶ added in v0.13.0
func NumberKeySchema() (*filter.ResourceSchema, error)
NumberKeySchema returns NumberKeyRecord's generated filter schema.
func Parser ¶
func Parser(t testing.TB) *filter.FilterParser
Parser returns the fixture's filter parser, clock pinned to Now.
func PathParser ¶
func PathParser(schema *filter.ResourceSchema) *filter.FilterParser
PathParser returns a filter parser over schema — PathSchema, or a copy declaring another storage — with its clock pinned to Now.
func PathSchema ¶
func PathSchema() (*filter.ResourceSchema, error)
PathSchema returns PathRecord's generated filter schema.
func RunArrays ¶ added in v0.12.0
func RunArrays(t *testing.T, schema *filter.ResourceSchema, backend Backend)
RunArrays evaluates every ArrayCases case on backend, which evaluates against ArrayRecords.
func RunDecimals ¶ added in v0.12.0
func RunDecimals(t *testing.T, schema *filter.ResourceSchema, backend Backend)
RunDecimals evaluates every DecimalCases case, parsed against schema (DecimalSchema or a copy declaring the backend's storage), on backend, which evaluates against DecimalRecords.
func RunMembership ¶
RunMembership evaluates every membership case on backend. The filter it passes still holds membership nodes: a SQL backend compiles them, a memory backend resolves them (filter.ResolveMemberships) first.
func RunPathSets ¶ added in v0.12.0
func RunPathSets(t *testing.T, schema *filter.ResourceSchema, backend Backend)
RunPathSets evaluates every PathSetCases case, parsed against schema (PathSchema or a copy declaring the backend's storage), on backend, which evaluates against PathRecords.
func RunPaths ¶
func RunPaths(t *testing.T, schema *filter.ResourceSchema, backend Backend)
RunPaths evaluates every path case, parsed against schema (PathSchema or a copy declaring the backend's storage), on backend, which evaluates against PathRecords.
func RunSets ¶ added in v0.12.0
RunSets evaluates every SetCases case on backend, which evaluates against Records.
func Schema ¶
func Schema() (*filter.ResourceSchema, error)
Schema returns Record's generated filter schema.
func SetConfig ¶ added in v0.12.0
func SetConfig() *core.ParserConfig
SetConfig is the parser configuration the set cases parse under: sets of up to 100,000 members and filters that size, clock pinned to Now.
Types ¶
type ArrayRecord ¶ added in v0.12.0
type ArrayRecord struct {
ID string `json:"id" db:"id" parse:"filter" filter_ops:"eq,in"`
Doc json.RawMessage `` /* 141-byte string literal not displayed */
Labels []string `json:"labels" db:"labels" parse:"filter,facet"`
Scores []int64 `json:"scores" db:"scores" parse:"filter,facet"`
Lines json.RawMessage `json:"lines" db:"lines" parse:"filter" elements:"sku:string;tags:[]string"`
}
ArrayRecord is the scalar-array fixture: a document whose members are arrays of strings, integers, decimals, timestamps and booleans, two array columns, and a JSON array of objects whose elements hold an array of strings. Its table is "array_records".
func ArrayRecords ¶ added in v0.12.0
func ArrayRecords() []ArrayRecord
ArrayRecords is the fixture data. An array member's value is its elements of its type, so these members hold none: c's empty arrays, d's JSON null and string, e's string and object, f's NULL document, h's absent counts, and i's [null]. g's elements are of mixed kinds: its tags hold one string ("A"), its counts 7.0 and 5.5, its prices 1e3 as a number (a string in exponent notation is no decimal). b's tags repeat an element. The array columns are NULL in d and empty in c and f. A line's tags follow the same rules: e's are a string, h's second line is no object, i's tags are empty, and f's lines are no array.
type Case ¶
type Case struct {
Name string
Filter string
Want []string
// ParseErr is the E-code the parser rejects Filter with; Want is unused.
ParseErr string
// Unsupported names the backends that cannot evaluate the filter and
// must fail instead ("sqlite", "memory"); a coded failure carries
// E2115.
Unsupported []string
}
Case is one filter and the IDs it matches, in ID order.
func ArrayCases ¶ added in v0.12.0
func ArrayCases() []Case
ArrayCases are filters on ArrayRecords' array members and columns, each also under $not: `$some` and `$none` are never Unknown, `$contains` is on a NULL array. Parse them under SetConfig: some `$contains` lists hold 100,000 values.
func Cases ¶
func Cases() []Case
Cases is the operator × type matrix over Records. Every predicate but the NULL tests is NULL on a NULL field, so negation never matches row d.
func DecimalCases ¶ added in v0.12.0
func DecimalCases() []Case
DecimalCases are filters on DecimalRecords' amount column and price member that a double would answer wrongly, and the rows each matches.
func PathCases ¶
func PathCases() []Case
PathCases are the path filters and the rows each matches. An absent member is NULL: no comparison but the NULL tests matches it.
func PathSetCases ¶ added in v0.12.0
func PathSetCases() []Case
PathSetCases are SetCases over PathRecords' typed paths: a string, int, float, decimal and UUID member. Parse them under SetConfig.
type DecimalRecord ¶ added in v0.12.0
type DecimalRecord struct {
ID string `json:"id" db:"id" parse:"filter,sort" filter_ops:"eq,in"`
Amount *decimal.Decimal `` /* 134-byte string literal not displayed */
Doc json.RawMessage `json:"doc" db:"doc" parse:"filter" paths:"price:decimal:filter+sort+facet"`
}
DecimalRecord is the exact-decimal fixture: a decimal column and a document whose price member holds a decimal as a JSON number, as a string in plain notation, or not at all. Its values sit where a double loses them: around 2^53 and 2^63, at 20 and 38 digits, and negatives that differ in the seventeenth digit.
func DecimalRecords ¶ added in v0.12.0
func DecimalRecords() []DecimalRecord
DecimalRecords is the fixture data. Amounts are written as decimal text, h's with a scale (10.50); n's equals h's. Prices are JSON numbers as written (h's 1.050e1, i's -0.0) or strings (b, d, f, n's 10.500); j's is a boolean, k's a string with an exponent, l's JSON null and m's absent, so those four hold no price.
type FacetOrder ¶ added in v0.13.0
FacetOrder is a terms facet requested with Limit and the keys it returns, in order: by count, ties in the order of the field's type. Its fixture's counts tie across keys whose text order and value order differ, and Limit cuts through the tie, so a backend that breaks ties by text fails it.
type LookupColumn ¶
LookupColumn is one lookup column and the type its values have: string, uuid (uuid.UUID values) or int (int64 values).
type LookupTable ¶
type LookupTable struct {
Name string
Columns []LookupColumn
Rows [][]any
}
LookupTable is a table membership cases select from. A backend creates it beside "records" with a column per LookupColumn and loads Rows; a nil value is NULL.
func LookupTables ¶
func LookupTables() []LookupTable
LookupTables are the shapes authorization resolves through: a reflexive closure over record ids, a pair relation with users, groups, tenants and ended pairs, grants of UUID references, and a nullable integer column.
type MembershipCase ¶
type MembershipCase struct {
Name string
Constraints *filter.PolicyConstraints
Want []string
}
MembershipCase applies Constraints to an empty filter over Records; Want is the matching IDs in ID order.
func MembershipCases ¶
func MembershipCases() []MembershipCase
MembershipCases are the membership semantics every backend shares: SQL's `x IN (subquery)`, three-valued under NOT when x or a projected value is NULL.
type NumberKeyRecord ¶ added in v0.13.0
type NumberKeyRecord struct {
ID string `json:"id" db:"id" parse:"filter"`
Attrs json.RawMessage `json:"attrs" db:"attrs" parse:"filter" paths:"n:int:facet;r:float:facet;ns:[]float:facet"`
F64 float64 `json:"f64" db:"f64" parse:"filter,facet"`
F32 float32 `json:"f32" db:"f32" parse:"filter,facet"`
I int64 `json:"i" db:"i" parse:"filter,facet"`
Ratios []float32 `json:"ratios" db:"ratios" parse:"filter,facet"`
}
NumberKeyRecord is the numeric facet key fixture: number members written several ways, float64, float32 and int columns, and a float32 array column. Its table is NumberKeySchema's.
func NumberKeyRecords ¶ added in v0.13.0
func NumberKeyRecords() []NumberKeyRecord
NumberKeyRecords is the fixture data. One value is written as 7, 7.0 and 7e0, another as 1.5, 1.50 and 15e-1, and zero as -0 and -0.0. f's members are a string and a boolean, d's ns a string, so they hold no number; g's document is NULL. The float64 column holds a double with 17 significant digits, 1e21, 1e-7 and both zeros; the float32 column 0.1, 1.5e-7 and 16777217 (16777216 as a float32), which a REAL holds as the double of the float32.
type PathRecord ¶
type PathRecord struct {
ID string `json:"id" db:"id" parse:"filter" filter_ops:"eq,in"`
Attrs json.RawMessage `` /* 213-byte string literal not displayed */
Settings *Settings `json:"settings" db:"settings" parse:"filter"`
}
PathRecord holds two documents: Attrs, a map with declared paths, and Settings, a struct whose members are paths. Its table is "path_records".
func PathRecords ¶
func PathRecords() []PathRecord
PathRecords is the path fixture. Rows c, d, f and h hold no typed member at any attrs path: c's members have other JSON kinds or shapes (its price a string in exponent notation), d's attrs is NULL, f's is empty and h's is an array; c's timestamp carries an offset. e's color is JSON null, its size is above 2^53 and its timestamp has a short fraction; g's size is a decimal, its color upper-case and its owner an upper-case UUID. a's timestamp is written as encoding/json writes UTC. Prices are JSON numbers (a, e) or strings in plain notation (b with a scale, g), as a decimal encodes.
type Record ¶
type Record struct {
ID string `json:"id" db:"id" parse:"filter" filter_ops:"eq,in"`
Name *string `json:"name" db:"name" parse:"filter" filter_ops:"eq,ne,gt,gte,lt,lte,in,nin,like,ilike,contains,null,not_null,exists"`
Count *int64 `json:"count" db:"count" parse:"filter" filter_ops:"eq,ne,gt,gte,lt,lte,in,nin,null,not_null,exists"`
Score *float64 `json:"score" db:"score" parse:"filter" filter_ops:"eq,ne,gt,gte,lt,lte,in,nin,null,not_null,exists"`
Amount *decimal.Decimal `json:"amount" db:"amount" parse:"filter" filter_ops:"eq,ne,gt,gte,lt,lte,in,nin,null,not_null,exists"`
Active *bool `json:"active" db:"active" parse:"filter" filter_ops:"eq,ne,in,nin,null,not_null,exists"`
At *time.Time `json:"at" db:"at" parse:"filter" filter_ops:"eq,ne,gt,gte,lt,lte,in,nin,null,not_null,exists"`
Ref *uuid.UUID `json:"ref" db:"ref" parse:"filter" filter_ops:"eq,ne,in,nin,null,not_null,exists"`
Doc json.RawMessage `json:"doc" db:"doc" parse:"filter" filter_ops:"eq,ne,contains,null,not_null,exists"`
Tags []string `json:"tags" db:"tags" parse:"filter" filter_ops:"contains,null,not_null,exists"`
Loc map[string]float64 `json:"loc" db:"loc" parse:"filter" filter_ops:"near,within,intersects,null,not_null,exists" geo:"jsonb"`
}
Record declares every operator its field type admits (filter.OperatorsFor), so the cases cover the full operator × type matrix. Its table is "records".
func Records ¶
func Records() []Record
Records is the fixture data. Row d is NULL in every column but id; g's document stores its integer as 1.0 with keys in a different order than the cases name them. The amounts order differently as numbers than as text (9.5 < 10 < 10.25), and h's is stored with a scale (10.00).
type Settings ¶
type Settings struct {
Mode string `json:"mode" parse:"filter,facet" filter_ops:"eq,ne,gt,lt,in,null,exists"`
Level int64 `json:"level" parse:"filter,sort,facet" facet:"range:0-5,5+"`
Window *Window `json:"window" parse:"filter"`
Note string `json:"note"`
}
Settings is a typed (struct) document; its tagged members are paths settings.mode, settings.level, settings.window.from.