mirror of
https://github.com/pikami/cosmium.git
synced 2025-06-07 16:10:23 +01:00
Compare commits
2 Commits
11851297f5
...
fba9b3df5f
Author | SHA1 | Date | |
---|---|---|---|
|
fba9b3df5f | ||
|
b743e23ff9 |
@ -1,12 +1,15 @@
|
|||||||
package badgerdatastore
|
package badgerdatastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
"github.com/pikami/cosmium/internal/logger"
|
"github.com/pikami/cosmium/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BadgerDataStore struct {
|
type BadgerDataStore struct {
|
||||||
db *badger.DB
|
db *badger.DB
|
||||||
|
gcTicker *time.Ticker
|
||||||
}
|
}
|
||||||
|
|
||||||
type BadgerDataStoreOptions struct {
|
type BadgerDataStoreOptions struct {
|
||||||
@ -25,12 +28,24 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BadgerDataStore{
|
gcTicker := time.NewTicker(5 * time.Minute)
|
||||||
db: db,
|
|
||||||
|
ds := &BadgerDataStore{
|
||||||
|
db: db,
|
||||||
|
gcTicker: gcTicker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go ds.runGarbageCollector()
|
||||||
|
|
||||||
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BadgerDataStore) Close() {
|
func (r *BadgerDataStore) Close() {
|
||||||
|
if r.gcTicker != nil {
|
||||||
|
r.gcTicker.Stop()
|
||||||
|
r.gcTicker = nil
|
||||||
|
}
|
||||||
|
|
||||||
r.db.Close()
|
r.db.Close()
|
||||||
r.db = nil
|
r.db = nil
|
||||||
}
|
}
|
||||||
@ -39,3 +54,13 @@ func (r *BadgerDataStore) DumpToJson() (string, error) {
|
|||||||
logger.ErrorLn("Badger datastore does not support state export currently.")
|
logger.ErrorLn("Badger datastore does not support state export currently.")
|
||||||
return "{}", nil
|
return "{}", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *BadgerDataStore) runGarbageCollector() {
|
||||||
|
for range r.gcTicker.C {
|
||||||
|
again:
|
||||||
|
err := r.db.RunValueLogGC(0.7)
|
||||||
|
if err == nil {
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -35,6 +35,7 @@ const (
|
|||||||
SelectItemTypeFunctionCall
|
SelectItemTypeFunctionCall
|
||||||
SelectItemTypeSubQuery
|
SelectItemTypeSubQuery
|
||||||
SelectItemTypeExpression
|
SelectItemTypeExpression
|
||||||
|
SelectItemTypeBinaryExpression
|
||||||
)
|
)
|
||||||
|
|
||||||
type SelectItem struct {
|
type SelectItem struct {
|
||||||
@ -65,6 +66,12 @@ type ComparisonExpression struct {
|
|||||||
Operation string
|
Operation string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BinaryExpression struct {
|
||||||
|
Left interface{}
|
||||||
|
Right interface{}
|
||||||
|
Operation string
|
||||||
|
}
|
||||||
|
|
||||||
type ConstantType int
|
type ConstantType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
366
parsers/nosql/arithmetics_test.go
Normal file
366
parsers/nosql/arithmetics_test.go
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
package nosql_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
testutils "github.com/pikami/cosmium/test_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Parse_Arithmetics(t *testing.T) {
|
||||||
|
t.Run("Should parse multiplication before addition", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a + c.b * c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "b"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should parse division before subtraction", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.x - c.y / c.z FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: testutils.SelectItem_Path("c", "x"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "/",
|
||||||
|
Left: testutils.SelectItem_Path("c", "y"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "z"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle complex mixed operations", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a + c.b * c.c - c.d / c.e FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "b"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "/",
|
||||||
|
Left: testutils.SelectItem_Path("c", "d"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "e"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should respect parentheses overriding precedence", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT (c.a + c.b) * c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle nested parentheses", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT ((c.a + c.b) * c.c) - c.d FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "d"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should be left associative for same precedence operators", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a - c.b - c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "-",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should be left associative with multiplication and division", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a * c.b / c.c FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "/",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Path("c", "c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle math with constants", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT 10 + 20 * 5 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Constant_Int(10),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Constant_Int(20),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle math with floating point numbers", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.price * 1.08 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "price"),
|
||||||
|
Right: testutils.SelectItem_Constant_Float(1.08),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle parentheses around single value", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT (c.value) FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "value"),
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle function calls in math expressions", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT LENGTH(c.name) * 2 + 10 FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeFunctionCall,
|
||||||
|
Value: parsers.FunctionCall{
|
||||||
|
Type: parsers.FunctionCallLength,
|
||||||
|
Arguments: []interface{}{testutils.SelectItem_Path("c", "name")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle multiple select items with math", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.a + c.b, c.x * c.y FROM c`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "b"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "x"),
|
||||||
|
Right: testutils.SelectItem_Path("c", "y"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should handle math in WHERE clause", func(t *testing.T) {
|
||||||
|
testQueryParse(
|
||||||
|
t,
|
||||||
|
`SELECT c.id FROM c WHERE c.price * 1.08 > 100`,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "id"),
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
Filters: parsers.ComparisonExpression{
|
||||||
|
Operation: ">",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "price"),
|
||||||
|
Right: testutils.SelectItem_Constant_Float(1.08),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -178,6 +178,32 @@ func combineExpressions(ex1 interface{}, exs interface{}, operation parsers.Logi
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeMathExpression(left interface{}, operations interface{}) (interface{}, error) {
|
||||||
|
if operations == nil || len(operations.([]interface{})) == 0 {
|
||||||
|
return left, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := left.(parsers.SelectItem)
|
||||||
|
ops := operations.([]interface{})
|
||||||
|
|
||||||
|
for _, op := range ops {
|
||||||
|
opData := op.([]interface{})
|
||||||
|
operation := opData[0].(string)
|
||||||
|
right := opData[1].(parsers.SelectItem)
|
||||||
|
|
||||||
|
result = parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Left: result,
|
||||||
|
Right: right,
|
||||||
|
Operation: operation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Input <- selectStmt:SelectStmt {
|
Input <- selectStmt:SelectStmt {
|
||||||
@ -387,10 +413,20 @@ AndExpression <- ex1:ComparisonExpression ex2:(ws And ws ex:ComparisonExpression
|
|||||||
return combineExpressions(ex1, ex2, parsers.LogicalExpressionTypeAnd)
|
return combineExpressions(ex1, ex2, parsers.LogicalExpressionTypeAnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil }
|
ComparisonExpression <- left:AddSubExpression ws op:ComparisonOperator ws right:AddSubExpression {
|
||||||
/ left:SelectItem ws op:ComparisonOperator ws right:SelectItem {
|
|
||||||
return parsers.ComparisonExpression{Left:left,Right:right,Operation:op.(string)}, nil
|
return parsers.ComparisonExpression{Left:left,Right:right,Operation:op.(string)}, nil
|
||||||
} / inv:(Not ws)? ex:SelectItem {
|
} / ex:AddSubExpression { return ex, nil }
|
||||||
|
|
||||||
|
AddSubExpression <- left:MulDivExpression operations:(ws op:AddOrSubtractOperation ws right:MulDivExpression { return []interface{}{op, right}, nil })* {
|
||||||
|
return makeMathExpression(left, operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
MulDivExpression <- left:SelectItemWithParentheses operations:(ws op:MultiplyOrDivideOperation ws right:SelectItemWithParentheses { return []interface{}{op, right}, nil })* {
|
||||||
|
return makeMathExpression(left, operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectItemWithParentheses <- "(" ws ex:OrExpression ws ")" { return ex, nil }
|
||||||
|
/ inv:(Not ws)? ex:SelectItem {
|
||||||
if inv != nil {
|
if inv != nil {
|
||||||
ex1 := ex.(parsers.SelectItem)
|
ex1 := ex.(parsers.SelectItem)
|
||||||
ex1.Invert = true
|
ex1.Invert = true
|
||||||
@ -447,6 +483,10 @@ ComparisonOperator <- ("<=" / ">=" / "=" / "!=" / "<" / ">") {
|
|||||||
return string(c.text), nil
|
return string(c.text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddOrSubtractOperation <- ("+" / "-") { return string(c.text), nil }
|
||||||
|
|
||||||
|
MultiplyOrDivideOperation <- ("*" / "/") { return string(c.text), nil }
|
||||||
|
|
||||||
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
|
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
|
||||||
|
|
||||||
ParameterConstant <- "@" Identifier {
|
ParameterConstant <- "@" Identifier {
|
||||||
|
91
query_executors/memory_executor/arithmetics_test.go
Normal file
91
query_executors/memory_executor/arithmetics_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package memoryexecutor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
|
||||||
|
testutils "github.com/pikami/cosmium/test_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Execute_Arithmetics(t *testing.T) {
|
||||||
|
mockData := []memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": 1, "a": 420},
|
||||||
|
map[string]interface{}{"id": 2, "a": 6.9},
|
||||||
|
map[string]interface{}{"id": 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Should execute simple arithmetics", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
{
|
||||||
|
Path: []string{"c", "id"},
|
||||||
|
Type: parsers.SelectItemTypeField,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Alias: "result",
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "+",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Constant_Float(2.0),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": 1, "result": 426.0},
|
||||||
|
map[string]interface{}{"id": 2, "result": 12.9},
|
||||||
|
map[string]interface{}{"id": 3, "result": nil},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute arithmetics in WHERE clause", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
SelectItems: []parsers.SelectItem{
|
||||||
|
testutils.SelectItem_Path("c", "id"),
|
||||||
|
{
|
||||||
|
Alias: "result",
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||||
|
Filters: parsers.ComparisonExpression{
|
||||||
|
Operation: ">",
|
||||||
|
Left: parsers.SelectItem{
|
||||||
|
Type: parsers.SelectItemTypeBinaryExpression,
|
||||||
|
Value: parsers.BinaryExpression{
|
||||||
|
Operation: "*",
|
||||||
|
Left: testutils.SelectItem_Path("c", "a"),
|
||||||
|
Right: testutils.SelectItem_Constant_Int(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: testutils.SelectItem_Constant_Int(500),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": 1, "result": 840.0},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
@ -82,6 +82,15 @@ func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{}
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selectItem.Type == parsers.SelectItemTypeBinaryExpression {
|
||||||
|
if typedSelectItem, ok := selectItem.Value.(parsers.BinaryExpression); ok {
|
||||||
|
return r.selectItem_SelectItemTypeBinaryExpression(typedSelectItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.ErrorLn("parsers.SelectItem has incorrect Value type (expected parsers.BinaryExpression)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return r.selectItem_SelectItemTypeField(selectItem)
|
return r.selectItem_SelectItemTypeField(selectItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +326,45 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r rowContext) selectItem_SelectItemTypeBinaryExpression(binaryExpression parsers.BinaryExpression) interface{} {
|
||||||
|
if binaryExpression.Left == nil || binaryExpression.Right == nil {
|
||||||
|
logger.Debug("parsers.BinaryExpression has nil Left or Right value")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leftValue := r.resolveSelectItem(binaryExpression.Left.(parsers.SelectItem))
|
||||||
|
rightValue := r.resolveSelectItem(binaryExpression.Right.(parsers.SelectItem))
|
||||||
|
|
||||||
|
if leftValue == nil || rightValue == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leftNumber, leftIsNumber := numToFloat64(leftValue)
|
||||||
|
rightNumber, rightIsNumber := numToFloat64(rightValue)
|
||||||
|
|
||||||
|
if !leftIsNumber || !rightIsNumber {
|
||||||
|
logger.Debug("Binary expression operands are not numbers, returning nil")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch binaryExpression.Operation {
|
||||||
|
case "+":
|
||||||
|
return leftNumber + rightNumber
|
||||||
|
case "-":
|
||||||
|
return leftNumber - rightNumber
|
||||||
|
case "*":
|
||||||
|
return leftNumber * rightNumber
|
||||||
|
case "/":
|
||||||
|
if rightNumber == 0 {
|
||||||
|
logger.Debug("Division by zero in binary expression")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return leftNumber / rightNumber
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem) interface{} {
|
func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem) interface{} {
|
||||||
value := r.tables[selectItem.Path[0]]
|
value := r.tables[selectItem.Path[0]]
|
||||||
|
|
||||||
@ -352,6 +400,7 @@ func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
func compareValues(val1, val2 interface{}) int {
|
func compareValues(val1, val2 interface{}) int {
|
||||||
|
// Handle nil values
|
||||||
if val1 == nil && val2 == nil {
|
if val1 == nil && val2 == nil {
|
||||||
return 0
|
return 0
|
||||||
} else if val1 == nil {
|
} else if val1 == nil {
|
||||||
@ -360,27 +409,24 @@ func compareValues(val1, val2 interface{}) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle number values
|
||||||
|
val1Number, val1IsNumber := numToFloat64(val1)
|
||||||
|
val2Number, val2IsNumber := numToFloat64(val2)
|
||||||
|
if val1IsNumber && val2IsNumber {
|
||||||
|
if val1Number < val2Number {
|
||||||
|
return -1
|
||||||
|
} else if val1Number > val2Number {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different types
|
||||||
if reflect.TypeOf(val1) != reflect.TypeOf(val2) {
|
if reflect.TypeOf(val1) != reflect.TypeOf(val2) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
switch val1 := val1.(type) {
|
switch val1 := val1.(type) {
|
||||||
case int:
|
|
||||||
val2 := val2.(int)
|
|
||||||
if val1 < val2 {
|
|
||||||
return -1
|
|
||||||
} else if val1 > val2 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case float64:
|
|
||||||
val2 := val2.(float64)
|
|
||||||
if val1 < val2 {
|
|
||||||
return -1
|
|
||||||
} else if val1 > val2 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case string:
|
case string:
|
||||||
val2 := val2.(string)
|
val2 := val2.(string)
|
||||||
return strings.Compare(val1, val2)
|
return strings.Compare(val1, val2)
|
||||||
|
@ -605,10 +605,30 @@ func numToInt(ex interface{}) (int, bool) {
|
|||||||
|
|
||||||
func numToFloat64(num interface{}) (float64, bool) {
|
func numToFloat64(num interface{}) (float64, bool) {
|
||||||
switch val := num.(type) {
|
switch val := num.(type) {
|
||||||
case float64:
|
|
||||||
return val, true
|
|
||||||
case int:
|
case int:
|
||||||
return float64(val), true
|
return float64(val), true
|
||||||
|
case int8:
|
||||||
|
return float64(val), true
|
||||||
|
case int16:
|
||||||
|
return float64(val), true
|
||||||
|
case int32:
|
||||||
|
return float64(val), true
|
||||||
|
case int64:
|
||||||
|
return float64(val), true
|
||||||
|
case uint:
|
||||||
|
return float64(val), true
|
||||||
|
case uint8:
|
||||||
|
return float64(val), true
|
||||||
|
case uint16:
|
||||||
|
return float64(val), true
|
||||||
|
case uint32:
|
||||||
|
return float64(val), true
|
||||||
|
case uint64:
|
||||||
|
return float64(val), true
|
||||||
|
case float32:
|
||||||
|
return float64(val), true
|
||||||
|
case float64:
|
||||||
|
return val, true
|
||||||
default:
|
default:
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user