mirror of
https://github.com/pikami/cosmium.git
synced 2025-12-19 17:00:37 +00:00
Added support for arithmetics inside queries
This commit is contained in:
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
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -387,10 +413,20 @@ AndExpression <- ex1:ComparisonExpression ex2:(ws And ws ex:ComparisonExpression
|
||||
return combineExpressions(ex1, ex2, parsers.LogicalExpressionTypeAnd)
|
||||
}
|
||||
|
||||
ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil }
|
||||
/ left:SelectItem ws op:ComparisonOperator ws right:SelectItem {
|
||||
ComparisonExpression <- left:AddSubExpression ws op:ComparisonOperator ws right:AddSubExpression {
|
||||
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 {
|
||||
ex1 := ex.(parsers.SelectItem)
|
||||
ex1.Invert = true
|
||||
@@ -447,6 +483,10 @@ ComparisonOperator <- ("<=" / ">=" / "=" / "!=" / "<" / ">") {
|
||||
return string(c.text), nil
|
||||
}
|
||||
|
||||
AddOrSubtractOperation <- ("+" / "-") { return string(c.text), nil }
|
||||
|
||||
MultiplyOrDivideOperation <- ("*" / "/") { return string(c.text), nil }
|
||||
|
||||
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
|
||||
|
||||
ParameterConstant <- "@" Identifier {
|
||||
|
||||
Reference in New Issue
Block a user