mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Replace SQLite with BadgerDB
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
package search
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"encoding/gob"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Expression interface {
|
||||
String() string
|
||||
@ -11,7 +15,7 @@ type PrefixExpression struct {
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) String() string {
|
||||
func (pe PrefixExpression) String() string {
|
||||
b := strings.Builder{}
|
||||
b.WriteString("(")
|
||||
b.WriteString(pe.Operator.String())
|
||||
@ -28,7 +32,7 @@ type InfixExpression struct {
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (ie *InfixExpression) String() string {
|
||||
func (ie InfixExpression) String() string {
|
||||
b := strings.Builder{}
|
||||
b.WriteString("(")
|
||||
b.WriteString(ie.Left.String())
|
||||
@ -45,6 +49,32 @@ type StringLiteral struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) String() string {
|
||||
func (sl StringLiteral) String() string {
|
||||
return sl.Value
|
||||
}
|
||||
|
||||
type RegexpLiteral struct {
|
||||
*regexp.Regexp
|
||||
}
|
||||
|
||||
func (rl RegexpLiteral) MarshalBinary() ([]byte, error) {
|
||||
return []byte(rl.Regexp.String()), nil
|
||||
}
|
||||
|
||||
func (rl *RegexpLiteral) UnmarshalBinary(data []byte) error {
|
||||
re, err := regexp.Compile(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*rl = RegexpLiteral{re}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(PrefixExpression{})
|
||||
gob.Register(InfixExpression{})
|
||||
gob.Register(StringLiteral{})
|
||||
gob.Register(RegexpLiteral{})
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type precedence int
|
||||
@ -99,7 +100,7 @@ func ParseQuery(input string) (expr Expression, err error) {
|
||||
case expr == nil:
|
||||
expr = right
|
||||
default:
|
||||
expr = &InfixExpression{
|
||||
expr = InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: expr,
|
||||
Right: right,
|
||||
@ -170,7 +171,7 @@ func (p *Parser) parseExpression(prec precedence) (Expression, error) {
|
||||
}
|
||||
|
||||
func parsePrefixExpression(p *Parser) (Expression, error) {
|
||||
expr := &PrefixExpression{
|
||||
expr := PrefixExpression{
|
||||
Operator: p.cur.Type,
|
||||
}
|
||||
|
||||
@ -187,7 +188,7 @@ func parsePrefixExpression(p *Parser) (Expression, error) {
|
||||
}
|
||||
|
||||
func parseInfixExpression(p *Parser, left Expression) (Expression, error) {
|
||||
expr := &InfixExpression{
|
||||
expr := InfixExpression{
|
||||
Operator: p.cur.Type,
|
||||
Left: left,
|
||||
}
|
||||
@ -200,13 +201,24 @@ func parseInfixExpression(p *Parser, left Expression) (Expression, error) {
|
||||
return nil, fmt.Errorf("could not parse expression for right operand: %w", err)
|
||||
}
|
||||
|
||||
if expr.Operator == TokOpRe || expr.Operator == TokOpNotRe {
|
||||
if rightStr, ok := right.(StringLiteral); ok {
|
||||
re, err := regexp.Compile(rightStr.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not compile regular expression %q: %w", rightStr.Value, err)
|
||||
}
|
||||
|
||||
right = re
|
||||
}
|
||||
}
|
||||
|
||||
expr.Right = right
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func parseStringLiteral(p *Parser) (Expression, error) {
|
||||
return &StringLiteral{Value: p.cur.Literal}, nil
|
||||
return StringLiteral{Value: p.cur.Literal}, nil
|
||||
}
|
||||
|
||||
func parseGroupedExpression(p *Parser) (Expression, error) {
|
||||
@ -227,7 +239,7 @@ func parseGroupedExpression(p *Parser) (Expression, error) {
|
||||
return nil, fmt.Errorf("could not parse expression: %w", err)
|
||||
}
|
||||
|
||||
expr = &InfixExpression{
|
||||
expr = InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: expr,
|
||||
Right: right,
|
||||
|
@ -3,6 +3,7 @@ package search
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -24,101 +25,101 @@ func TestParseQuery(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression",
|
||||
input: "foobar",
|
||||
expectedExpression: &StringLiteral{Value: "foobar"},
|
||||
expectedExpression: StringLiteral{Value: "foobar"},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with equal operator",
|
||||
input: "foo = bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpEq,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with not equal operator",
|
||||
input: "foo != bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpNotEq,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with greater than operator",
|
||||
input: "foo > bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpGt,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with less than operator",
|
||||
input: "foo < bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpLt,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with greater than or equal operator",
|
||||
input: "foo >= bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpGtEq,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with less than or equal operator",
|
||||
input: "foo <= bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpLtEq,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with regular expression operator",
|
||||
input: "foo =~ bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpRe,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: regexp.MustCompile("bar"),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with not regular expression operator",
|
||||
input: "foo !~ bar",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpNotRe,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: regexp.MustCompile("bar"),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with AND, OR and NOT operators",
|
||||
input: "foo AND bar OR NOT baz",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &InfixExpression{
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: InfixExpression{
|
||||
Operator: TokOpOr,
|
||||
Left: &StringLiteral{Value: "bar"},
|
||||
Right: &PrefixExpression{
|
||||
Left: StringLiteral{Value: "bar"},
|
||||
Right: PrefixExpression{
|
||||
Operator: TokOpNot,
|
||||
Right: &StringLiteral{Value: "baz"},
|
||||
Right: StringLiteral{Value: "baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -127,16 +128,16 @@ func TestParseQuery(t *testing.T) {
|
||||
{
|
||||
name: "boolean expression with nested group",
|
||||
input: "(foo AND bar) OR NOT baz",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpOr,
|
||||
Left: &InfixExpression{
|
||||
Left: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: &PrefixExpression{
|
||||
Right: PrefixExpression{
|
||||
Operator: TokOpNot,
|
||||
Right: &StringLiteral{Value: "baz"},
|
||||
Right: StringLiteral{Value: "baz"},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
@ -144,59 +145,59 @@ func TestParseQuery(t *testing.T) {
|
||||
{
|
||||
name: "implicit boolean expression with string literal operands",
|
||||
input: "foo bar baz",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &InfixExpression{
|
||||
Left: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: &StringLiteral{Value: "baz"},
|
||||
Right: StringLiteral{Value: "baz"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "implicit boolean expression nested in group",
|
||||
input: "(foo bar)",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "implicit and explicit boolean expression with string literal operands",
|
||||
input: "foo bar OR baz yolo",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &InfixExpression{
|
||||
Left: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &InfixExpression{
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: InfixExpression{
|
||||
Operator: TokOpOr,
|
||||
Left: &StringLiteral{Value: "bar"},
|
||||
Right: &StringLiteral{Value: "baz"},
|
||||
Left: StringLiteral{Value: "bar"},
|
||||
Right: StringLiteral{Value: "baz"},
|
||||
},
|
||||
},
|
||||
Right: &StringLiteral{Value: "yolo"},
|
||||
Right: StringLiteral{Value: "yolo"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "implicit boolean expression with comparison operands",
|
||||
input: "foo=bar baz=~yolo",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: &InfixExpression{
|
||||
Left: InfixExpression{
|
||||
Operator: TokOpEq,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: &InfixExpression{
|
||||
Right: InfixExpression{
|
||||
Operator: TokOpRe,
|
||||
Left: &StringLiteral{Value: "baz"},
|
||||
Right: &StringLiteral{Value: "yolo"},
|
||||
Left: StringLiteral{Value: "baz"},
|
||||
Right: regexp.MustCompile("yolo"),
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
@ -204,17 +205,17 @@ func TestParseQuery(t *testing.T) {
|
||||
{
|
||||
name: "eq operator takes precedence over boolean ops",
|
||||
input: "foo=bar OR baz=yolo",
|
||||
expectedExpression: &InfixExpression{
|
||||
expectedExpression: InfixExpression{
|
||||
Operator: TokOpOr,
|
||||
Left: &InfixExpression{
|
||||
Left: InfixExpression{
|
||||
Operator: TokOpEq,
|
||||
Left: &StringLiteral{Value: "foo"},
|
||||
Right: &StringLiteral{Value: "bar"},
|
||||
Left: StringLiteral{Value: "foo"},
|
||||
Right: StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: &InfixExpression{
|
||||
Right: InfixExpression{
|
||||
Operator: TokOpEq,
|
||||
Left: &StringLiteral{Value: "baz"},
|
||||
Right: &StringLiteral{Value: "yolo"},
|
||||
Left: StringLiteral{Value: "baz"},
|
||||
Right: StringLiteral{Value: "yolo"},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
|
Reference in New Issue
Block a user