Documentation
¶
Overview ¶
Package query contains methods for building queries executed by the Index.Query method.
Index ¶
- type Aggregator
- type Condition
- type Contains
- type Mapper
- type Match
- type Pred
- type Query
- type QueryBuilder
- func (builder *QueryBuilder[K, F]) Aggregate(aggregator Aggregator[K, F]) *QueryBuilder[K, F]
- func (builder *QueryBuilder[K, F]) Query() Query[K, F]
- func (builder *QueryBuilder[K, F]) StreamTo(reducer Reducer[K, F]) *streamBuilder[K, F]
- func (builder *QueryBuilder[K, F]) Where(condition Condition[K, F]) *QueryBuilder[K, F]
- type Reducer
- type Result
- type ResultEntry
- type ResultKey
- type StreamAggregator
- type UpdateFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Aggregator ¶
Aggregator is a map-reduce operation. It allows filtering and aggregation of results.
type Mapper ¶
type Mapper[K feature.Key, F feature.Feature[K]] interface { Map(match *Match[K, F]) (*Match[K, F], error) }
Mapper transforms a match, updating Match.ResultKeys.
type Match ¶
type Match[K feature.Key, F feature.Feature[K]] struct { ResultKeys []ResultKey Feature F Meta interface{} }
func (*Match[K, F]) ReplaceKeys ¶
type Pred ¶
Pred is a predicate condition letting use a function instead of creating a structure implementing the Condition interface.
type Query ¶
type Query[K feature.Key, F feature.Feature[K]] struct { Conditions []Condition[K, F] // Logical conjunction (AND). Aggregators []Aggregator[K, F] // Logical disjunction (OR). // contains filtered or unexported fields }
Query holds configuration of a query pipeline for narrowing down spatial search results.
type QueryBuilder ¶
type QueryBuilder[K feature.Key, F feature.Feature[K]] struct { // contains filtered or unexported fields }
func Build ¶
func Build[K feature.Key, F feature.Feature[K]]() *QueryBuilder[K, F]
Build returns a new query builder.
Example (ComplexAggregation) ¶
This example uses an example spatial feature implementation. See https://github.com/bilus/fencer/blob/master/query/query_test.go for more details.
package main
import (
"fmt"
"sort"
"strconv"
"github.com/bilus/fencer/primitives"
"github.com/bilus/fencer/query"
"github.com/bilus/fencer/testutil"
)
type CountryID int
func (id CountryID) String() string {
return strconv.Itoa(int(id))
}
type Country struct {
ID CountryID
Name string
Population int
Change float64
Region string
BoundingRect *primitives.Rect
}
func (c Country) Key() CountryID {
return c.ID
}
func (c Country) Contains(p primitives.Point) (bool, error) {
return testutil.Contains(*c.BoundingRect, p), nil
}
func (c Country) Bounds() *primitives.Rect {
return c.BoundingRect
}
var countries = []Country{
{1, "Vatican City", 800, -0.011, "Europe", makeRect(bounds[0])},
{2, "Tokelau", 1300, 0.014, "Polynesia", makeRect(bounds[1])},
{3, "Niue", 1600, -0.004, "Polynesia", makeRect(bounds[2])},
{4, "Tuvalu", 11200, 0.009, "Oceania", makeRect(bounds[3])},
{5, "Nauru", 11300, 0.001, "Oceania", makeRect(bounds[4])},
{6, "Poland", 38224, -0.001, "Europe", makeRect(bounds[5])},
{7, "Ukraine", 44400, 0, "Europe", makeRect(bounds[6])},
}
type GroupByRegion struct{}
func (GroupByRegion) Map(match *query.Match[CountryID, Country]) (*query.Match[CountryID, Country], error) {
match.ReplaceKeys(match.Feature.Region)
return match, nil
}
type MostPopulated struct{}
func (MostPopulated) Reduce(result *query.Result[CountryID, Country], match *query.Match[CountryID, Country]) error {
for _, key := range match.ResultKeys {
err := result.Update(key, func(entry *query.ResultEntry[CountryID, Country]) error {
if len(entry.Features) == 0 {
entry.Features = []Country{match.Feature}
return nil
}
existingCountry := entry.Features[0]
currentCountry := match.Feature
if existingCountry.Population < currentCountry.Population {
entry.Features = []Country{match.Feature}
}
return nil
})
if err != nil {
return err
}
}
return nil
}
type DecliningPopulation struct{}
func (DecliningPopulation) Map(match *query.Match[CountryID, Country]) (*query.Match[CountryID, Country], error) {
country := match.Feature
if country.Change < 0 {
match.AddKey("declining")
}
return match, nil
}
func main() {
qb := query.Build[CountryID, Country]()
stream := qb.StreamTo(MostPopulated{})
stream.Map(GroupByRegion{})
stream.Map(DecliningPopulation{})
query := qb.Query() // Map(MostPopulatedByRegion{}).Query()
for _, country := range countries {
query.Scan(country)
}
// Output will include Poland which isn't the largest country in its region
// but it's the largest one with declining population.
fmt.Println("Most populated countries per region and most populated country with declining population:", len(query.Distinct()))
printNamesSorted(query.Distinct())
}
var bounds = [][]primitives.Point{
{
{12.44450569152832, 41.89978557507729},
{12.459547519683836, 41.89978557507729},
{12.459547519683836, 41.907946360630994},
{12.44450569152832, 41.907946360630994},
},
{
{-172.7874755859375, -9.66573839518868},
{-170.947265625, -9.66573839518868},
{-170.947265625, -8.303905908124174},
{-172.7874755859375, -8.303905908124174},
},
{
{-170.13702392578125, -19.265776189877485},
{-169.5849609375, -19.265776189877485},
{-169.5849609375, -18.818567424622376},
{-170.13702392578125, -18.818567424622376},
},
{
{174.74853515625, -11.059820828563412},
{180.296630859375, -11.059820828563412},
{180.296630859375, -5.397273407690904},
{174.74853515625, -5.397273407690904},
},
{
{166.79443359375, -0.6227752122036241},
{167.07595825195312, -0.6227752122036241},
{167.07595825195312, -0.4051174740026618},
{166.79443359375, -0.4051174740026618},
},
{
{14.04052734375, 48.922499263758255},
{24.27978515625, 48.922499263758255},
{24.27978515625, 54.99022172004893},
{14.04052734375, 54.99022172004893},
},
{
{21.665039062499996, 44.02442151965934},
{40.341796875, 44.02442151965934},
{40.341796875, 52.482780222078226},
{21.665039062499996, 52.482780222078226},
},
}
func makeRect(points []primitives.Point) *primitives.Rect {
p := points[0]
op := points[2]
rect, err := primitives.NewRect(p, op[0]-p[0], op[1]-p[1])
if err != nil {
panic(err)
}
return rect
}
func printNamesSorted(features []Country) {
names := make([]string, 0)
for _, f := range features {
names = append(names, f.Name)
}
sort.Strings(names)
for _, name := range names {
fmt.Println(name)
}
}
Output: Most populated countries per region and most populated country with declining population: 4 Nauru Niue Poland Ukraine
Example (Conjunction) ¶
This example uses an example spatial feature implementation. See https://github.com/bilus/fencer/blob/master/query/query_test.go for more details.
package main
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/bilus/fencer/primitives"
"github.com/bilus/fencer/query"
"github.com/bilus/fencer/testutil"
)
type CountryID int
func (id CountryID) String() string {
return strconv.Itoa(int(id))
}
type Country struct {
ID CountryID
Name string
Population int
Change float64
Region string
BoundingRect *primitives.Rect
}
func (c Country) Key() CountryID {
return c.ID
}
func (c Country) Contains(p primitives.Point) (bool, error) {
return testutil.Contains(*c.BoundingRect, p), nil
}
func (c Country) Bounds() *primitives.Rect {
return c.BoundingRect
}
var countries = []Country{
{1, "Vatican City", 800, -0.011, "Europe", makeRect(bounds[0])},
{2, "Tokelau", 1300, 0.014, "Polynesia", makeRect(bounds[1])},
{3, "Niue", 1600, -0.004, "Polynesia", makeRect(bounds[2])},
{4, "Tuvalu", 11200, 0.009, "Oceania", makeRect(bounds[3])},
{5, "Nauru", 11300, 0.001, "Oceania", makeRect(bounds[4])},
{6, "Poland", 38224, -0.001, "Europe", makeRect(bounds[5])},
{7, "Ukraine", 44400, 0, "Europe", makeRect(bounds[6])},
}
func main() {
// Both preconditions must match.
q := query.Build[CountryID, Country]()
q.Where(
query.Pred[CountryID, Country](func(feature Country) (bool, error) {
return feature.Population > 10000, nil
}))
q.Where(
query.Pred[CountryID, Country](func(feature Country) (bool, error) {
return strings.HasPrefix(feature.Name, "T"), nil
}),
)
query := q.Query()
for _, country := range countries {
query.Scan(country)
}
fmt.Println("Countries with population > 10000 with name beginning with T:", len(query.Distinct()))
printNamesSorted(query.Distinct())
}
var bounds = [][]primitives.Point{
{
{12.44450569152832, 41.89978557507729},
{12.459547519683836, 41.89978557507729},
{12.459547519683836, 41.907946360630994},
{12.44450569152832, 41.907946360630994},
},
{
{-172.7874755859375, -9.66573839518868},
{-170.947265625, -9.66573839518868},
{-170.947265625, -8.303905908124174},
{-172.7874755859375, -8.303905908124174},
},
{
{-170.13702392578125, -19.265776189877485},
{-169.5849609375, -19.265776189877485},
{-169.5849609375, -18.818567424622376},
{-170.13702392578125, -18.818567424622376},
},
{
{174.74853515625, -11.059820828563412},
{180.296630859375, -11.059820828563412},
{180.296630859375, -5.397273407690904},
{174.74853515625, -5.397273407690904},
},
{
{166.79443359375, -0.6227752122036241},
{167.07595825195312, -0.6227752122036241},
{167.07595825195312, -0.4051174740026618},
{166.79443359375, -0.4051174740026618},
},
{
{14.04052734375, 48.922499263758255},
{24.27978515625, 48.922499263758255},
{24.27978515625, 54.99022172004893},
{14.04052734375, 54.99022172004893},
},
{
{21.665039062499996, 44.02442151965934},
{40.341796875, 44.02442151965934},
{40.341796875, 52.482780222078226},
{21.665039062499996, 52.482780222078226},
},
}
func makeRect(points []primitives.Point) *primitives.Rect {
p := points[0]
op := points[2]
rect, err := primitives.NewRect(p, op[0]-p[0], op[1]-p[1])
if err != nil {
panic(err)
}
return rect
}
func printNamesSorted(features []Country) {
names := make([]string, 0)
for _, f := range features {
names = append(names, f.Name)
}
sort.Strings(names)
for _, name := range names {
fmt.Println(name)
}
}
Output: Countries with population > 10000 with name beginning with T: 1 Tuvalu
Example (GroupingResults) ¶
This example uses an example spatial feature implementation. See https://github.com/bilus/fencer/blob/master/query/query_test.go for more details.
package main
import (
"fmt"
"sort"
"strconv"
"github.com/bilus/fencer/primitives"
"github.com/bilus/fencer/query"
"github.com/bilus/fencer/testutil"
)
type CountryID int
func (id CountryID) String() string {
return strconv.Itoa(int(id))
}
type Country struct {
ID CountryID
Name string
Population int
Change float64
Region string
BoundingRect *primitives.Rect
}
func (c Country) Key() CountryID {
return c.ID
}
func (c Country) Contains(p primitives.Point) (bool, error) {
return testutil.Contains(*c.BoundingRect, p), nil
}
func (c Country) Bounds() *primitives.Rect {
return c.BoundingRect
}
var countries = []Country{
{1, "Vatican City", 800, -0.011, "Europe", makeRect(bounds[0])},
{2, "Tokelau", 1300, 0.014, "Polynesia", makeRect(bounds[1])},
{3, "Niue", 1600, -0.004, "Polynesia", makeRect(bounds[2])},
{4, "Tuvalu", 11200, 0.009, "Oceania", makeRect(bounds[3])},
{5, "Nauru", 11300, 0.001, "Oceania", makeRect(bounds[4])},
{6, "Poland", 38224, -0.001, "Europe", makeRect(bounds[5])},
{7, "Ukraine", 44400, 0, "Europe", makeRect(bounds[6])},
}
type GroupByRegion struct{}
func (GroupByRegion) Map(match *query.Match[CountryID, Country]) (*query.Match[CountryID, Country], error) {
match.ReplaceKeys(match.Feature.Region)
return match, nil
}
type MostPopulated struct{}
func (MostPopulated) Reduce(result *query.Result[CountryID, Country], match *query.Match[CountryID, Country]) error {
for _, key := range match.ResultKeys {
err := result.Update(key, func(entry *query.ResultEntry[CountryID, Country]) error {
if len(entry.Features) == 0 {
entry.Features = []Country{match.Feature}
return nil
}
existingCountry := entry.Features[0]
currentCountry := match.Feature
if existingCountry.Population < currentCountry.Population {
entry.Features = []Country{match.Feature}
}
return nil
})
if err != nil {
return err
}
}
return nil
}
func main() {
qb := query.Build[CountryID, Country]()
stream := qb.StreamTo(MostPopulated{})
stream.Map(GroupByRegion{})
query := qb.Query() // Map(MostPopulatedByRegion{}).Query()
for _, country := range countries {
query.Scan(country)
}
fmt.Println("Most populated countries per region:", len(query.Distinct()))
printNamesSorted(query.Distinct())
}
var bounds = [][]primitives.Point{
{
{12.44450569152832, 41.89978557507729},
{12.459547519683836, 41.89978557507729},
{12.459547519683836, 41.907946360630994},
{12.44450569152832, 41.907946360630994},
},
{
{-172.7874755859375, -9.66573839518868},
{-170.947265625, -9.66573839518868},
{-170.947265625, -8.303905908124174},
{-172.7874755859375, -8.303905908124174},
},
{
{-170.13702392578125, -19.265776189877485},
{-169.5849609375, -19.265776189877485},
{-169.5849609375, -18.818567424622376},
{-170.13702392578125, -18.818567424622376},
},
{
{174.74853515625, -11.059820828563412},
{180.296630859375, -11.059820828563412},
{180.296630859375, -5.397273407690904},
{174.74853515625, -5.397273407690904},
},
{
{166.79443359375, -0.6227752122036241},
{167.07595825195312, -0.6227752122036241},
{167.07595825195312, -0.4051174740026618},
{166.79443359375, -0.4051174740026618},
},
{
{14.04052734375, 48.922499263758255},
{24.27978515625, 48.922499263758255},
{24.27978515625, 54.99022172004893},
{14.04052734375, 54.99022172004893},
},
{
{21.665039062499996, 44.02442151965934},
{40.341796875, 44.02442151965934},
{40.341796875, 52.482780222078226},
{21.665039062499996, 52.482780222078226},
},
}
func makeRect(points []primitives.Point) *primitives.Rect {
p := points[0]
op := points[2]
rect, err := primitives.NewRect(p, op[0]-p[0], op[1]-p[1])
if err != nil {
panic(err)
}
return rect
}
func printNamesSorted(features []Country) {
names := make([]string, 0)
for _, f := range features {
names = append(names, f.Name)
}
sort.Strings(names)
for _, name := range names {
fmt.Println(name)
}
}
Output: Most populated countries per region: 3 Nauru Niue Ukraine
Example (PreconditionsUsingPredicates) ¶
This example uses an example spatial feature implementation. See https://github.com/bilus/fencer/blob/master/query/query_test.go for more details.
package main
import (
"fmt"
"strconv"
"github.com/bilus/fencer/primitives"
"github.com/bilus/fencer/query"
"github.com/bilus/fencer/testutil"
)
type CountryID int
func (id CountryID) String() string {
return strconv.Itoa(int(id))
}
type Country struct {
ID CountryID
Name string
Population int
Change float64
Region string
BoundingRect *primitives.Rect
}
func (c Country) Key() CountryID {
return c.ID
}
func (c Country) Contains(p primitives.Point) (bool, error) {
return testutil.Contains(*c.BoundingRect, p), nil
}
func (c Country) Bounds() *primitives.Rect {
return c.BoundingRect
}
var countries = []Country{
{1, "Vatican City", 800, -0.011, "Europe", makeRect(bounds[0])},
{2, "Tokelau", 1300, 0.014, "Polynesia", makeRect(bounds[1])},
{3, "Niue", 1600, -0.004, "Polynesia", makeRect(bounds[2])},
{4, "Tuvalu", 11200, 0.009, "Oceania", makeRect(bounds[3])},
{5, "Nauru", 11300, 0.001, "Oceania", makeRect(bounds[4])},
{6, "Poland", 38224, -0.001, "Europe", makeRect(bounds[5])},
{7, "Ukraine", 44400, 0, "Europe", makeRect(bounds[6])},
}
func main() {
query := query.Build[CountryID, Country]().Where(
query.Pred[CountryID, Country](func(country Country) (bool, error) {
return country.Population > 10000, nil
}),
).Query()
for _, country := range countries {
query.Scan(country)
}
fmt.Println("Countries with population > 10000:", len(query.Distinct()))
}
var bounds = [][]primitives.Point{
{
{12.44450569152832, 41.89978557507729},
{12.459547519683836, 41.89978557507729},
{12.459547519683836, 41.907946360630994},
{12.44450569152832, 41.907946360630994},
},
{
{-172.7874755859375, -9.66573839518868},
{-170.947265625, -9.66573839518868},
{-170.947265625, -8.303905908124174},
{-172.7874755859375, -8.303905908124174},
},
{
{-170.13702392578125, -19.265776189877485},
{-169.5849609375, -19.265776189877485},
{-169.5849609375, -18.818567424622376},
{-170.13702392578125, -18.818567424622376},
},
{
{174.74853515625, -11.059820828563412},
{180.296630859375, -11.059820828563412},
{180.296630859375, -5.397273407690904},
{174.74853515625, -5.397273407690904},
},
{
{166.79443359375, -0.6227752122036241},
{167.07595825195312, -0.6227752122036241},
{167.07595825195312, -0.4051174740026618},
{166.79443359375, -0.4051174740026618},
},
{
{14.04052734375, 48.922499263758255},
{24.27978515625, 48.922499263758255},
{24.27978515625, 54.99022172004893},
{14.04052734375, 54.99022172004893},
},
{
{21.665039062499996, 44.02442151965934},
{40.341796875, 44.02442151965934},
{40.341796875, 52.482780222078226},
{21.665039062499996, 52.482780222078226},
},
}
func makeRect(points []primitives.Point) *primitives.Rect {
p := points[0]
op := points[2]
rect, err := primitives.NewRect(p, op[0]-p[0], op[1]-p[1])
if err != nil {
panic(err)
}
return rect
}
Output: Countries with population > 10000: 4
Example (PreconditionsUsingStructs) ¶
This example uses an example spatial feature implementation. See https://github.com/bilus/fencer/blob/master/query/query_test.go for more details.
package main
import (
"fmt"
"sort"
"strconv"
"github.com/bilus/fencer/primitives"
"github.com/bilus/fencer/query"
"github.com/bilus/fencer/testutil"
)
type CountryID int
func (id CountryID) String() string {
return strconv.Itoa(int(id))
}
type Country struct {
ID CountryID
Name string
Population int
Change float64
Region string
BoundingRect *primitives.Rect
}
func (c Country) Key() CountryID {
return c.ID
}
func (c Country) Contains(p primitives.Point) (bool, error) {
return testutil.Contains(*c.BoundingRect, p), nil
}
func (c Country) Bounds() *primitives.Rect {
return c.BoundingRect
}
var countries = []Country{
{1, "Vatican City", 800, -0.011, "Europe", makeRect(bounds[0])},
{2, "Tokelau", 1300, 0.014, "Polynesia", makeRect(bounds[1])},
{3, "Niue", 1600, -0.004, "Polynesia", makeRect(bounds[2])},
{4, "Tuvalu", 11200, 0.009, "Oceania", makeRect(bounds[3])},
{5, "Nauru", 11300, 0.001, "Oceania", makeRect(bounds[4])},
{6, "Poland", 38224, -0.001, "Europe", makeRect(bounds[5])},
{7, "Ukraine", 44400, 0, "Europe", makeRect(bounds[6])},
}
type PopulationGreaterThan struct {
threshold int
}
func (p PopulationGreaterThan) IsMatch(feature Country) (bool, error) {
return feature.Population > p.threshold, nil
}
func main() {
// This is how you implement struct conditions:
//
// type PopulationGreaterThan struct {
// threshold int
// }
// func (p PopulationGreaterThan) IsMatch(feature feature.Feature) (bool, error) {
// return feature.(*Country).Population > p.threshold, nil
// }
query := query.Build[CountryID, Country]().Where(PopulationGreaterThan{10000}).Query()
for _, country := range countries {
query.Scan(country)
}
fmt.Println("Countries with population > 10000:", len(query.Distinct()))
printNamesSorted(query.Distinct())
}
var bounds = [][]primitives.Point{
{
{12.44450569152832, 41.89978557507729},
{12.459547519683836, 41.89978557507729},
{12.459547519683836, 41.907946360630994},
{12.44450569152832, 41.907946360630994},
},
{
{-172.7874755859375, -9.66573839518868},
{-170.947265625, -9.66573839518868},
{-170.947265625, -8.303905908124174},
{-172.7874755859375, -8.303905908124174},
},
{
{-170.13702392578125, -19.265776189877485},
{-169.5849609375, -19.265776189877485},
{-169.5849609375, -18.818567424622376},
{-170.13702392578125, -18.818567424622376},
},
{
{174.74853515625, -11.059820828563412},
{180.296630859375, -11.059820828563412},
{180.296630859375, -5.397273407690904},
{174.74853515625, -5.397273407690904},
},
{
{166.79443359375, -0.6227752122036241},
{167.07595825195312, -0.6227752122036241},
{167.07595825195312, -0.4051174740026618},
{166.79443359375, -0.4051174740026618},
},
{
{14.04052734375, 48.922499263758255},
{24.27978515625, 48.922499263758255},
{24.27978515625, 54.99022172004893},
{14.04052734375, 54.99022172004893},
},
{
{21.665039062499996, 44.02442151965934},
{40.341796875, 44.02442151965934},
{40.341796875, 52.482780222078226},
{21.665039062499996, 52.482780222078226},
},
}
func makeRect(points []primitives.Point) *primitives.Rect {
p := points[0]
op := points[2]
rect, err := primitives.NewRect(p, op[0]-p[0], op[1]-p[1])
if err != nil {
panic(err)
}
return rect
}
func printNamesSorted(features []Country) {
names := make([]string, 0)
for _, f := range features {
names = append(names, f.Name)
}
sort.Strings(names)
for _, name := range names {
fmt.Println(name)
}
}
Output: Countries with population > 10000: 4 Nauru Poland Tuvalu Ukraine
func (*QueryBuilder[K, F]) Aggregate ¶
func (builder *QueryBuilder[K, F]) Aggregate(aggregator Aggregator[K, F]) *QueryBuilder[K, F]
Aggregate adds a new aggregator.
func (*QueryBuilder[K, F]) Query ¶
func (builder *QueryBuilder[K, F]) Query() Query[K, F]
Query returns a complete constructed query.
func (*QueryBuilder[K, F]) StreamTo ¶
func (builder *QueryBuilder[K, F]) StreamTo(reducer Reducer[K, F]) *streamBuilder[K, F]
StreamTo creates a new aggregator stream and returns its builder.
func (*QueryBuilder[K, F]) Where ¶
func (builder *QueryBuilder[K, F]) Where(condition Condition[K, F]) *QueryBuilder[K, F]
Where adds a filter to the query. Multiple filters act as a logical AND.
type Reducer ¶
type Reducer[K feature.Key, F feature.Feature[K]] interface { Reduce(result *Result[K, F], match *Match[K, F]) error }
Reducer updates result based on a match.
type Result ¶
type Result[K feature.Key, F feature.Feature[K]] struct { // contains filtered or unexported fields }
Result is a map of keys and the corresponding entries containing a query result.
type ResultEntry ¶
ResultEntry is a list of features associated with a result key with metadata user code can use for caching etc.
type ResultKey ¶
type ResultKey any
ResultKey is a key generated by a mapper, associated with a feature.
type StreamAggregator ¶
type StreamAggregator[K feature.Key, F feature.Feature[K]] struct { Mappers []Mapper[K, F] Reducer[K, F] }
StreamAggregator is a an aggregator supporting a sequence of mappers.
type UpdateFunc ¶
UpdateFunc is a callback passed to Result.Update.