filtertest

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2026 License: MIT Imports: 14 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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 Run

func Run(t *testing.T, name string, backend Backend)

Run evaluates every case on backend, named as Case.Unsupported names it.

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

func RunMembership(t *testing.T, backend Backend)

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

func RunSets(t *testing.T, name string, backend Backend)

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 Backend

type Backend func(t *testing.T, f *filter.ParsedFilter) ([]string, error)

Backend evaluates a parsed filter over Records and returns the matching IDs.

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.

func SetCases added in v0.12.0

func SetCases() []Case

SetCases are `$in` and `$nin` over every scalar column of Records, one case per size in SetSizes. 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

type FacetOrder struct {
	Limit int
	Keys  []string
}

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

type LookupColumn struct {
	Name string
	Type core.FieldType
}

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.

type Window

type Window struct {
	From time.Time `json:"from" parse:"filter"`
}

Window is a nested struct member of Settings.

Jump to

Keyboard shortcuts

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