match

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Apr 6, 2025 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Example
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type MatchUser struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

// createSampleUsers returns a slice of sample users for examples.
func createSampleUsers() []MatchUser {
	return []MatchUser{
		{
			ID:       1,
			Name:     "John Doe",
			Age:      30,
			Score:    4.5,
			Location: "New York",
			Role:     "admin",
			Verified: true,
			Premium:  true,
		},
		{
			ID:       2,
			Name:     "Jane Smith",
			Age:      25,
			Score:    3.8,
			Location: "Los Angeles",
			Role:     "user",
			Verified: true,
			Premium:  false,
		},
		{
			ID:       3,
			Name:     "Bob Johnson",
			Age:      35,
			Score:    4.2,
			Location: "Chicago",
			Role:     "user",
			Verified: false,
			Premium:  false,
		},

		{
			ID:       4,
			Name:     "Alice Smith",
			Age:      25,
			Score:    3.8,
			Location: "Los Angeles",
			Role:     "admin",
			Verified: false,
			Premium:  true,
		},
	}
}

func main() {
	// Define sample users
	users := createSampleUsers()

	q := `(age >= 30 and score > 4.0) or (location:"Los Angeles" and role:"user")`
	ast, _ := query.Parse("test", []byte(q))
	expr := ast.(query.Expr)

	matcher := &match.StructMatcher{}

	filtered := make([]MatchUser, 0, len(users))

	for _, user := range users {
		if expr.Match(&user, matcher) {
			filtered = append(filtered, user)
		}
	}

	// Print each match on a separate line to avoid long line warnings
	for i, u := range filtered {
		fmt.Printf("Match %d: %v\n", i+1, u)
	}
}
Output:

Match 1: {1 John Doe 30 4.5 New York admin true true}
Match 2: {2 Jane Smith 25 3.8 Los Angeles user true false}
Match 3: {3 Bob Johnson 35 4.2 Chicago user false false}
Example (BooleanFields)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type MatchUser struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	users := []MatchUser{
		{
			ID:       1,
			Name:     "John Doe",
			Age:      30,
			Score:    4.5,
			Location: "New York",
			Role:     "admin",
			Verified: true,
			Premium:  true,
		},
		{
			ID:       2,
			Name:     "Jane Smith",
			Age:      25,
			Score:    3.8,
			Location: "Los Angeles",
			Role:     "user",
			Verified: true,
			Premium:  false,
		},
		{
			ID:       3,
			Name:     "Bob Johnson",
			Age:      35,
			Score:    4.2,
			Location: "Chicago",
			Role:     "user",
			Verified: false,
			Premium:  false,
		},
	}

	// Boolean fields with shorthand syntax
	q := `verified and (premium or role:"user")`
	ast, _ := query.Parse("test", []byte(q))
	expr := ast.(query.Expr)

	matcher := &match.StructMatcher{}

	filtered := make([]MatchUser, 0, len(users))

	for _, user := range users {
		if expr.Match(&user, matcher) {
			filtered = append(filtered, user)
		}
	}

	// Print each match on a separate line to avoid long line warnings
	for i, u := range filtered {
		fmt.Printf("Match %d: %v\n", i+1, u)
	}
}
Output:

Match 1: {1 John Doe 30 4.5 New York admin true true}
Match 2: {2 Jane Smith 25 3.8 Los Angeles user true false}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrFieldNotFound = errors.New("field not found")
	ErrNotAStruct    = errors.New("not a struct")
)

Functions

func Path added in v0.5.0

func Path(s string) iter.Seq[string]

Types

type ReflectRouter added in v0.5.0

type ReflectRouter struct{}

ReflectRouter implements Router for struct targets. It supports struct tags using the `dumbql` tag name and nested field access using dot notation.

func (*ReflectRouter) Route added in v0.5.0

func (r *ReflectRouter) Route(target any, field string) (any, error)

Route resolves a field path in the target struct and returns the value. It supports nested field access using dot notation (e.g., "address.city").

type Router added in v0.5.0

type Router interface {
	// Route resolves a field path in the target object and returns the value.
	// The boolean return value indicates whether the field was successfully resolved.
	Route(target any, field string) (any, error)
}

Router is responsible for resolving a field path to a value in the target object. It abstracts the field resolution logic from the matcher implementation.

type StructMatcher

type StructMatcher struct {
	// contains filtered or unexported fields
}

StructMatcher is a basic implementation of the Matcher interface for evaluating query expressions against structs. It supports struct tags using the `dumbql` tag name, which allows you to specify a custom field name.

func NewStructMatcher added in v0.5.0

func NewStructMatcher(router Router) *StructMatcher

func (*StructMatcher) MatchAnd

func (m *StructMatcher) MatchAnd(target any, left, right query.Expr) bool

func (*StructMatcher) MatchField

func (m *StructMatcher) MatchField(target any, field string, value query.Valuer, op query.FieldOperator) bool

MatchField matches a field in the target struct using the provided value and operator. It supports struct tags using the `dumbql` tag name and nested field access using dot notation. For example: "address.city" to access the city field in the address struct.

Example (BooleanFields)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
		Verified: true,
		Premium:  false,
	}

	// Test boolean field expressions
	queries := []string{
		// Standard boolean comparison
		`verified:true`,
		`premium:false`,
		// Not equal comparison
		`verified!=false`,
		`premium!=true`,
		// Boolean field shorthand syntax
		`verified`,
		`not premium`,
		// Complex expressions with boolean shorthand
		`verified and not premium`,
		`verified and role:"admin"`,
		`verified and (age > 25 or location:"New York")`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'verified:true' match result: true
Query 'premium:false' match result: true
Query 'verified!=false' match result: true
Query 'premium!=true' match result: true
Query 'verified' match result: true
Query 'not premium' match result: true
Query 'verified and not premium' match result: true
Query 'verified and role:"admin"' match result: true
Query 'verified and (age > 25 or location:"New York")' match result: true
Example (ComplexMatching)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

func main() {
	type Address struct {
		Street  string `dumbql:"street"`
		City    string `dumbql:"city"`
		Country string `dumbql:"country"`
		Zip     string `dumbql:"zip"`
	}

	type UserWithAddress struct {
		ID       int64   `dumbql:"id"`
		Name     string  `dumbql:"name"`
		Age      int64   `dumbql:"age"`
		Score    float64 `dumbql:"score"`
		Location string  `dumbql:"location"`
		Role     string  `dumbql:"role"`
		Verified bool    `dumbql:"verified"`
		Premium  bool    `dumbql:"premium"`
		Address  Address `dumbql:"address"`
	}

	user := &UserWithAddress{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
		Verified: true,
		Premium:  false,
		Address: Address{
			Street:  "123 Main St",
			City:    "New York",
			Country: "USA",
			Zip:     "10001",
		},
	}

	// Parse a complex query with multiple conditions including nested field traversal
	// This demonstrates the nested field access capability using dot notation
	q := `age >= 25 and address.city:"New York" and score > 4.0`
	ast, _ := query.Parse("test", []byte(q))
	expr := ast.(query.Expr)

	// Create a matcher
	matcher := &match.StructMatcher{}
	result := expr.Match(user, matcher)

	fmt.Printf("%s: %v\n", q, result)
}
Output:

age >= 25 and address.city:"New York" and score > 4.0: true
Example (EdgeCases)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
	}
	// Test edge cases and special scenarios
	queries := []string{
		// Non-existent field
		`nonexistent:"value"`,
		// Invalid type comparison
		`age:"not a number"`,
		// Empty string matching
		`name:""`,
		// Zero value matching
		`score:0`,
		// Complex nested expression
		`(age > 20 and age < 40) and (score >= 4.0 or role:"admin")`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'nonexistent:"value"' match result: true
Query 'age:"not a number"' match result: false
Query 'name:""' match result: false
Query 'score:0' match result: false
Query '(age > 20 and age < 40) and (score >= 4.0 or role:"admin")' match result: true
Example (MultiMatch)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	users := []User{
		{
			ID:       1,
			Name:     "John Doe",
			Age:      30,
			Score:    4.5,
			Location: "New York",
			Role:     "admin",
		},
		{
			ID:       2,
			Name:     "Jane Smith",
			Age:      25,
			Score:    3.8,
			Location: "Los Angeles",
			Role:     "user",
		},
		{
			ID:       3,
			Name:     "Bob Johnson",
			Age:      35,
			Score:    4.2,
			Location: "Chicago",
			Role:     "user",
		},
		// This one will be dropped:
		{
			ID:       4,
			Name:     "Alice Smith",
			Age:      25,
			Score:    3.8,
			Location: "Los Angeles",
			Role:     "admin",
		},
	}

	q := `(age >= 30 and score > 4.0) or (location:"Los Angeles" and role:"user")`
	ast, _ := query.Parse("test", []byte(q))
	expr := ast.(query.Expr)

	matcher := &match.StructMatcher{}

	filtered := make([]User, 0, len(users))

	for _, user := range users {
		if expr.Match(&user, matcher) {
			filtered = append(filtered, user)
		}
	}

	// Print each match on a separate line to avoid long line warnings
	for i, u := range filtered {
		fmt.Printf("Match %d: %v\n", i+1, u)
	}
}
Output:

Match 1: {1 John Doe 30 4.5 New York admin false false}
Match 2: {2 Jane Smith 25 3.8 Los Angeles user false false}
Match 3: {3 Bob Johnson 35 4.2 Chicago user false false}
Example (NotExpressions)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
	}

	// Test NOT expressions
	queries := []string{
		`not age < 25`,
		`not location:"Los Angeles"`,
		`not (role:"user" and score < 3.0)`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'not age < 25' match result: true
Query 'not location:"Los Angeles"' match result: true
Query 'not (role:"user" and score < 3.0)' match result: true
Example (NumericComparisons)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
		Verified: true,
		Premium:  false,
	}

	// Test various numeric comparisons
	queries := []string{
		`age > 20`,
		`age < 40`,
		`age >= 30`,
		`age <= 30`,
		`score > 4.0`,
		`score < 5.0`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'age > 20' match result: true
Query 'age < 40' match result: true
Query 'age >= 30' match result: true
Query 'age <= 30' match result: true
Query 'score > 4.0' match result: true
Query 'score < 5.0' match result: true
Example (OneOfExpression)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
	}

	// Test OneOf expressions
	queries := []string{
		`location:["New York", "Los Angeles", "Chicago"]`,
		`role:["admin", "superuser"]`,
		`age:[25, 30, 35]`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'location:["New York", "Los Angeles", "Chicago"]' match result: true
Query 'role:["admin", "superuser"]' match result: true
Query 'age:[25, 30, 35]' match result: true
Example (SimpleMatching)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
		Verified: true,
		Premium:  false,
	}

	// Parse a simple equality query
	q := `name = "John Doe"`
	ast, _ := query.Parse("test", []byte(q))
	expr := ast.(query.Expr)

	// Create a matcher
	matcher := &match.StructMatcher{}
	result := expr.Match(user, matcher)

	fmt.Printf("%s: %v\n", q, result)
}
Output:

name = "John Doe": true
Example (StringOperations)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

type User struct {
	ID       int64   `dumbql:"id"`
	Name     string  `dumbql:"name"`
	Age      int64   `dumbql:"age"`
	Score    float64 `dumbql:"score"`
	Location string  `dumbql:"location"`
	Role     string  `dumbql:"role"`
	Verified bool    `dumbql:"verified"`
	Premium  bool    `dumbql:"premium"`
}

func main() {
	user := &User{
		ID:       1,
		Name:     "John Doe",
		Age:      30,
		Score:    4.5,
		Location: "New York",
		Role:     "admin",
	}

	// Test various string operations
	queries := []string{
		`name:"John Doe"`,
		`name~"John"`,
		`location:"New York"`,
		`role:admin`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'name:"John Doe"' match result: true
Query 'name~"John"' match result: true
Query 'location:"New York"' match result: true
Query 'role:admin' match result: true
Example (StructTagOmit)
package main

import (
	"fmt"

	"go.tomakado.io/dumbql/match"
	"go.tomakado.io/dumbql/query"
)

func main() {
	type User struct {
		ID       int64   `dumbql:"id"`
		Name     string  `dumbql:"name"`
		Password string  `dumbql:"-"` // Omitted from querying
		Internal bool    `dumbql:"-"` // Omitted from querying
		Score    float64 `dumbql:"score"`
	}

	user := &User{
		ID:       1,
		Name:     "John",
		Password: "secret123",
		Internal: true,
		Score:    4.5,
	}

	// Test various queries including omitted fields
	queries := []string{
		// Query against visible field
		`id:1`,
		// Query against omitted field - always matches
		`password:"wrong_password"`,
		// Query against omitted boolean field - always matches
		`internal:false`,
		// Combined visible and omitted fields
		`id:1 and password:"wrong_password"`,
		// Complex query with omitted fields
		`(id:1 or score > 4.0) and (password:"wrong" or internal:false)`,
	}

	matcher := &match.StructMatcher{}

	for _, q := range queries {
		ast, _ := query.Parse("test", []byte(q))
		expr := ast.(query.Expr)
		result := expr.Match(user, matcher)
		fmt.Printf("Query '%s' match result: %v\n", q, result)
	}
}
Output:

Query 'id:1' match result: true
Query 'password:"wrong_password"' match result: true
Query 'internal:false' match result: true
Query 'id:1 and password:"wrong_password"' match result: true
Query '(id:1 or score > 4.0) and (password:"wrong" or internal:false)' match result: true

func (*StructMatcher) MatchNot

func (m *StructMatcher) MatchNot(target any, expr query.Expr) bool

func (*StructMatcher) MatchOr

func (m *StructMatcher) MatchOr(target any, left, right query.Expr) bool

func (*StructMatcher) MatchValue

func (m *StructMatcher) MatchValue(target any, value query.Valuer, op query.FieldOperator) bool

Jump to

Keyboard shortcuts

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