Implement Mathematical Functions

This commit is contained in:
Pijus Kamandulis 2024-06-19 00:44:46 +03:00
parent b808e97c72
commit 3bdff9b643
9 changed files with 5089 additions and 663 deletions

View File

@ -1,6 +1,6 @@
# Cosmium # Cosmium
Cosmium is a lightweight Cosmos DB emulator designed to facilitate local development and testing. While it aims to provide developers with a solution for running a local database during development, it's important to note that it's not 100% compatible with Cosmos DB. However, it serves as a convenient tool for E2E or integration tests during the CI/CD pipeline. Read more about compatibility [here](./docs/compatibility.md). Cosmium is a lightweight Cosmos DB emulator designed to facilitate local development and testing. While it aims to provide developers with a solution for running a local database during development, it's important to note that it's not 100% compatible with Cosmos DB. However, it serves as a convenient tool for E2E or integration tests during the CI/CD pipeline. Read more about compatibility [here](./docs/COMPATIBILITY.md).
One of Cosmium's notable features is its ability to save and load state to a single JSON file. This feature makes it easy to load different test cases or share state with other developers, enhancing collaboration and efficiency in development workflows. One of Cosmium's notable features is its ability to save and load state to a single JSON file. This feature makes it easy to load different test cases or share state with other developers, enhancing collaboration and efficiency in development workflows.

View File

@ -15,8 +15,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
## Compatibility Matrix ## Compatibility Matrix
### Features ### Features
| Feature | Implemented | | Feature | Implemented |
|-------------------------------|-------------| | ----------------------------- | ----------- |
| Subqueries | No | | Subqueries | No |
| Joins | No | | Joins | No |
| Computed properties | No | | Computed properties | No |
@ -29,8 +30,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| User-defined functions (UDFs) | No | | User-defined functions (UDFs) | No |
### Clauses ### Clauses
| Clause | Implemented | | Clause | Implemented |
|--------------|-------------| | ------------ | ----------- |
| SELECT | Yes | | SELECT | Yes |
| FROM | Yes | | FROM | Yes |
| WHERE | Yes | | WHERE | Yes |
@ -39,8 +41,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| OFFSET LIMIT | Yes | | OFFSET LIMIT | Yes |
### Keywords ### Keywords
| Keyword | Implemented | | Keyword | Implemented |
|----------|-------------| | -------- | ----------- |
| BETWEEN | No | | BETWEEN | No |
| DISTINCT | Yes | | DISTINCT | Yes |
| LIKE | No | | LIKE | No |
@ -48,8 +51,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| TOP | Yes | | TOP | Yes |
### Aggregate Functions ### Aggregate Functions
| Function | Implemented | | Function | Implemented |
|----------|-------------| | -------- | ----------- |
| AVG | Yes | | AVG | Yes |
| COUNT | Yes | | COUNT | Yes |
| MAX | Yes | | MAX | Yes |
@ -57,8 +61,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| SUM | Yes | | SUM | Yes |
### Array Functions ### Array Functions
| Function | Implemented | | Function | Implemented |
|----------------|-------------| | -------------- | ----------- |
| ARRAY_CONCAT | Yes | | ARRAY_CONCAT | Yes |
| ARRAY_CONTAINS | No | | ARRAY_CONTAINS | No |
| ARRAY_LENGTH | Yes | | ARRAY_LENGTH | Yes |
@ -69,13 +74,15 @@ Cosmium strives to support the core features of Cosmos DB, including:
| SetUnion | Yes | | SetUnion | Yes |
### Conditional Functions ### Conditional Functions
| Function | Implemented | | Function | Implemented |
|----------|-------------| | -------- | ----------- |
| IIF | No | | IIF | No |
### Date and time Functions ### Date and time Functions
| Function | Implemented | | Function | Implemented |
|---------------------------|-------------| | ------------------------- | ----------- |
| DateTimeAdd | No | | DateTimeAdd | No |
| DateTimeBin | No | | DateTimeBin | No |
| DateTimeDiff | No | | DateTimeDiff | No |
@ -93,53 +100,56 @@ Cosmium strives to support the core features of Cosmos DB, including:
| TimestampToDateTime | No | | TimestampToDateTime | No |
### Item Functions ### Item Functions
| Function | Implemented | | Function | Implemented |
|------------|-------------| | ---------- | ----------- |
| DocumentId | No | | DocumentId | No |
### Mathematical Functions ### Mathematical Functions
| Function | Implemented | | Function | Implemented |
|------------------|-------------| | ---------------- | ----------- |
| ABS | No | | ABS | Yes |
| ACOS | No | | ACOS | Yes |
| ASIN | No | | ASIN | Yes |
| ATAN | No | | ATAN | Yes |
| ATN2 | No | | ATN2 | Yes |
| CEILING | No | | CEILING | Yes |
| COS | No | | COS | Yes |
| COT | No | | COT | Yes |
| DEGREES | No | | DEGREES | Yes |
| EXP | No | | EXP | Yes |
| FLOOR | No | | FLOOR | Yes |
| IntAdd | No | | IntAdd | Yes |
| IntBitAnd | No | | IntBitAnd | Yes |
| IntBitLeftShift | No | | IntBitLeftShift | Yes |
| IntBitNot | No | | IntBitNot | Yes |
| IntBitOr | No | | IntBitOr | Yes |
| IntBitRightShift | No | | IntBitRightShift | Yes |
| IntBitXor | No | | IntBitXor | Yes |
| IntDiv | No | | IntDiv | Yes |
| IntMod | No | | IntMod | Yes |
| IntMul | No | | IntMul | Yes |
| IntSub | No | | IntSub | Yes |
| LOG | No | | LOG | Yes |
| LOG10 | No | | LOG10 | Yes |
| NumberBin | No | | NumberBin | Yes |
| PI | No | | PI | Yes |
| POWER | No | | POWER | Yes |
| RADIANS | No | | RADIANS | Yes |
| RAND | No | | RAND | Yes |
| ROUND | No | | ROUND | Yes |
| SIGN | No | | SIGN | Yes |
| SIN | No | | SIN | Yes |
| SQRT | No | | SQRT | Yes |
| SQUARE | No | | SQUARE | Yes |
| TAN | No | | TAN | Yes |
| TRUNC | No | | TRUNC | Yes |
### Spatial Functions ### Spatial Functions
| Function | Implemented | | Function | Implemented |
|--------------------|-------------| | ------------------ | ----------- |
| ST_AREA | No | | ST_AREA | No |
| ST_DISTANCE | No | | ST_DISTANCE | No |
| ST_WITHIN | No | | ST_WITHIN | No |
@ -148,8 +158,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| ST_ISVALIDDETAILED | No | | ST_ISVALIDDETAILED | No |
### String Functions ### String Functions
| Function | Implemented | | Function | Implemented |
|-----------------|-------------| | --------------- | ----------- |
| CONCAT | Yes | | CONCAT | Yes |
| CONTAINS | Yes | | CONTAINS | Yes |
| ENDSWITH | Yes | | ENDSWITH | Yes |
@ -177,8 +188,9 @@ Cosmium strives to support the core features of Cosmos DB, including:
| UPPER | Yes | | UPPER | Yes |
### Type checking Functions ### Type checking Functions
| Function | Implemented | | Function | Implemented |
|------------------|-------------| | ---------------- | ----------- |
| IS_ARRAY | Yes | | IS_ARRAY | Yes |
| IS_BOOL | Yes | | IS_BOOL | Yes |
| IS_DEFINED | Yes | | IS_DEFINED | Yes |

View File

@ -120,6 +120,43 @@ const (
FunctionCallSetIntersect FunctionCallType = "SetIntersect" FunctionCallSetIntersect FunctionCallType = "SetIntersect"
FunctionCallSetUnion FunctionCallType = "SetUnion" FunctionCallSetUnion FunctionCallType = "SetUnion"
FunctionCallMathAbs FunctionCallType = "MathAbs"
FunctionCallMathAcos FunctionCallType = "MathAcos"
FunctionCallMathAsin FunctionCallType = "MathAsin"
FunctionCallMathAtan FunctionCallType = "MathAtan"
FunctionCallMathAtn2 FunctionCallType = "MathAtn2"
FunctionCallMathCeiling FunctionCallType = "MathCeiling"
FunctionCallMathCos FunctionCallType = "MathCos"
FunctionCallMathCot FunctionCallType = "MathCot"
FunctionCallMathDegrees FunctionCallType = "MathDegrees"
FunctionCallMathExp FunctionCallType = "MathExp"
FunctionCallMathFloor FunctionCallType = "MathFloor"
FunctionCallMathIntAdd FunctionCallType = "MathIntAdd"
FunctionCallMathIntBitAnd FunctionCallType = "MathIntBitAnd"
FunctionCallMathIntBitLeftShift FunctionCallType = "MathIntBitLeftShift"
FunctionCallMathIntBitNot FunctionCallType = "MathIntBitNot"
FunctionCallMathIntBitOr FunctionCallType = "MathIntBitOr"
FunctionCallMathIntBitRightShift FunctionCallType = "MathIntBitRightShift"
FunctionCallMathIntBitXor FunctionCallType = "MathIntBitXor"
FunctionCallMathIntDiv FunctionCallType = "MathIntDiv"
FunctionCallMathIntMod FunctionCallType = "MathIntMod"
FunctionCallMathIntMul FunctionCallType = "MathIntMul"
FunctionCallMathIntSub FunctionCallType = "MathIntSub"
FunctionCallMathLog FunctionCallType = "MathLog"
FunctionCallMathLog10 FunctionCallType = "MathLog10"
FunctionCallMathNumberBin FunctionCallType = "MathNumberBin"
FunctionCallMathPi FunctionCallType = "MathPi"
FunctionCallMathPower FunctionCallType = "MathPower"
FunctionCallMathRadians FunctionCallType = "MathRadians"
FunctionCallMathRand FunctionCallType = "MathRand"
FunctionCallMathRound FunctionCallType = "MathRound"
FunctionCallMathSign FunctionCallType = "MathSign"
FunctionCallMathSin FunctionCallType = "MathSin"
FunctionCallMathSqrt FunctionCallType = "MathSqrt"
FunctionCallMathSquare FunctionCallType = "MathSquare"
FunctionCallMathTan FunctionCallType = "MathTan"
FunctionCallMathTrunc FunctionCallType = "MathTrunc"
FunctionCallAggregateAvg FunctionCallType = "AggregateAvg" FunctionCallAggregateAvg FunctionCallType = "AggregateAvg"
FunctionCallAggregateCount FunctionCallType = "AggregateCount" FunctionCallAggregateCount FunctionCallType = "AggregateCount"
FunctionCallAggregateMax FunctionCallType = "AggregateMax" FunctionCallAggregateMax FunctionCallType = "AggregateMax"

View File

@ -0,0 +1,650 @@
package nosql_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
)
func Test_Execute_MathFunctions(t *testing.T) {
t.Run("Should parse function ABS(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT ABS(c.value) FROM c`,
parsers.FunctionCallMathAbs,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function ACOS(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT ACOS(c.value) FROM c`,
parsers.FunctionCallMathAcos,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function ASIN(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT ASIN(c.value) FROM c`,
parsers.FunctionCallMathAsin,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function ATAN(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT ATAN(c.value) FROM c`,
parsers.FunctionCallMathAtan,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function CEILING(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT CEILING(c.value) FROM c`,
parsers.FunctionCallMathCeiling,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function COS(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT COS(c.value) FROM c`,
parsers.FunctionCallMathCos,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function COT(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT COT(c.value) FROM c`,
parsers.FunctionCallMathCot,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function DEGREES(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT DEGREES(c.value) FROM c`,
parsers.FunctionCallMathDegrees,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function EXP(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT EXP(c.value) FROM c`,
parsers.FunctionCallMathExp,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function FLOOR(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT FLOOR(c.value) FROM c`,
parsers.FunctionCallMathFloor,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntBitNot(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntBitNot(c.value) FROM c`,
parsers.FunctionCallMathIntBitNot,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function LOG10(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT LOG10(c.value) FROM c`,
parsers.FunctionCallMathLog10,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function RADIANS(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT RADIANS(c.value) FROM c`,
parsers.FunctionCallMathRadians,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function ROUND(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT ROUND(c.value) FROM c`,
parsers.FunctionCallMathRound,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function SIGN(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT SIGN(c.value) FROM c`,
parsers.FunctionCallMathSign,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function SIN(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT SIN(c.value) FROM c`,
parsers.FunctionCallMathSin,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function SQRT(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT SQRT(c.value) FROM c`,
parsers.FunctionCallMathSqrt,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function SQUARE(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT SQUARE(c.value) FROM c`,
parsers.FunctionCallMathSquare,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function TAN(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT TAN(c.value) FROM c`,
parsers.FunctionCallMathTan,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function TRUNC(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT TRUNC(c.value) FROM c`,
parsers.FunctionCallMathTrunc,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function ATN2(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT ATN2(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathAtn2,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntAdd(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntAdd(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntAdd,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntBitAnd(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntBitAnd(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntBitAnd,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntBitLeftShift(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntBitLeftShift(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntBitLeftShift,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntBitOr(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntBitOr(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntBitOr,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntBitRightShift(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntBitRightShift(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntBitRightShift,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntBitXor(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntBitXor(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntBitXor,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntDiv(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntDiv(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntDiv,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntMod(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntMod(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntMod,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntMul(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntMul(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntMul,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function IntSub(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT IntSub(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathIntSub,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function POWER(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT POWER(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathPower,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function LOG(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT LOG(c.value) FROM c`,
parsers.FunctionCallMathLog,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function LOG(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT LOG(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathLog,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function NumberBin(ex)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT NumberBin(c.value) FROM c`,
parsers.FunctionCallMathNumberBin,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function NumberBin(ex1, ex2)", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT NumberBin(c.value, c.secondValue) FROM c`,
parsers.FunctionCallMathNumberBin,
[]interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "secondValue"},
Type: parsers.SelectItemTypeField,
},
},
"c",
)
})
t.Run("Should parse function PI()", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT PI() FROM c`,
parsers.FunctionCallMathPi,
[]interface{}{},
"c",
)
})
t.Run("Should parse function RAND()", func(t *testing.T) {
testMathFunctionParse(
t,
`SELECT RAND() FROM c`,
parsers.FunctionCallMathRand,
[]interface{}{},
"c",
)
})
}
func testMathFunctionParse(
t *testing.T,
query string,
expectedFunctionType parsers.FunctionCallType,
expectedArguments []interface{},
expectedTable string,
) {
testQueryParse(
t,
query,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: expectedFunctionType,
Arguments: expectedArguments,
},
},
},
Table: parsers.Table{Value: expectedTable},
},
)
}

File diff suppressed because it is too large Load Diff

View File

@ -343,6 +343,7 @@ FunctionCall <- StringFunctions
/ ArrayFunctions / ArrayFunctions
/ InFunction / InFunction
/ AggregateFunctions / AggregateFunctions
/ MathFunctions
StringFunctions <- StringEqualsExpression StringFunctions <- StringEqualsExpression
/ ToStringExpression / ToStringExpression
@ -384,6 +385,43 @@ ArrayFunctions <- ArrayConcatExpression
/ SetIntersectExpression / SetIntersectExpression
/ SetUnionExpression / SetUnionExpression
MathFunctions <- MathAbsExpression
/ MathAcosExpression
/ MathAsinExpression
/ MathAtanExpression
/ MathCeilingExpression
/ MathCosExpression
/ MathCotExpression
/ MathDegreesExpression
/ MathExpExpression
/ MathFloorExpression
/ MathIntBitNotExpression
/ MathLog10Expression
/ MathRadiansExpression
/ MathRoundExpression
/ MathSignExpression
/ MathSinExpression
/ MathSqrtExpression
/ MathSquareExpression
/ MathTanExpression
/ MathTruncExpression
/ MathAtn2Expression
/ MathIntAddExpression
/ MathIntBitAndExpression
/ MathIntBitLeftShiftExpression
/ MathIntBitOrExpression
/ MathIntBitRightShiftExpression
/ MathIntBitXorExpression
/ MathIntDivExpression
/ MathIntModExpression
/ MathIntMulExpression
/ MathIntSubExpression
/ MathPowerExpression
/ MathLogExpression
/ MathNumberBinExpression
/ MathPiExpression
/ MathRandExpression
UpperExpression <- "UPPER"i ws "(" ex:SelectItem ")" { UpperExpression <- "UPPER"i ws "(" ex:SelectItem ")" {
return createFunctionCall(parsers.FunctionCallUpper, []interface{}{ex}) return createFunctionCall(parsers.FunctionCallUpper, []interface{}{ex})
} }
@ -527,6 +565,49 @@ SetUnionExpression <- "SetUnion"i ws "(" ws set1:SelectItem ws "," ws set2:Selec
return createFunctionCall(parsers.FunctionCallSetUnion, []interface{}{set1, set2}) return createFunctionCall(parsers.FunctionCallSetUnion, []interface{}{set1, set2})
} }
MathAbsExpression <- "ABS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAbs, []interface{}{ex}) }
MathAcosExpression <- "ACOS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAcos, []interface{}{ex}) }
MathAsinExpression <- "ASIN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAsin, []interface{}{ex}) }
MathAtanExpression <- "ATAN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAtan, []interface{}{ex}) }
MathCeilingExpression <- "CEILING"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathCeiling, []interface{}{ex}) }
MathCosExpression <- "COS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathCos, []interface{}{ex}) }
MathCotExpression <- "COT"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathCot, []interface{}{ex}) }
MathDegreesExpression <- "DEGREES"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathDegrees, []interface{}{ex}) }
MathExpExpression <- "EXP"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathExp, []interface{}{ex}) }
MathFloorExpression <- "FLOOR"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathFloor, []interface{}{ex}) }
MathIntBitNotExpression <- "IntBitNot"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntBitNot, []interface{}{ex}) }
MathLog10Expression <- "LOG10"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathLog10, []interface{}{ex}) }
MathRadiansExpression <- "RADIANS"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathRadians, []interface{}{ex}) }
MathRoundExpression <- "ROUND"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathRound, []interface{}{ex}) }
MathSignExpression <- "SIGN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathSign, []interface{}{ex}) }
MathSinExpression <- "SIN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathSin, []interface{}{ex}) }
MathSqrtExpression <- "SQRT"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathSqrt, []interface{}{ex}) }
MathSquareExpression <- "SQUARE"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathSquare, []interface{}{ex}) }
MathTanExpression <- "TAN"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathTan, []interface{}{ex}) }
MathTruncExpression <- "TRUNC"i ws "(" ws ex:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathTrunc, []interface{}{ex}) }
MathAtn2Expression <- "ATN2"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathAtn2, []interface{}{set1, set2}) }
MathIntAddExpression <- "IntAdd"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntAdd, []interface{}{set1, set2}) }
MathIntBitAndExpression <- "IntBitAnd"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntBitAnd, []interface{}{set1, set2}) }
MathIntBitLeftShiftExpression <- "IntBitLeftShift"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntBitLeftShift, []interface{}{set1, set2}) }
MathIntBitOrExpression <- "IntBitOr"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntBitOr, []interface{}{set1, set2}) }
MathIntBitRightShiftExpression <- "IntBitRightShift"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntBitRightShift, []interface{}{set1, set2}) }
MathIntBitXorExpression <- "IntBitXor"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntBitXor, []interface{}{set1, set2}) }
MathIntDivExpression <- "IntDiv"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntDiv, []interface{}{set1, set2}) }
MathIntModExpression <- "IntMod"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntMod, []interface{}{set1, set2}) }
MathIntMulExpression <- "IntMul"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntMul, []interface{}{set1, set2}) }
MathIntSubExpression <- "IntSub"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathIntSub, []interface{}{set1, set2}) }
MathPowerExpression <- "POWER"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" { return createFunctionCall(parsers.FunctionCallMathPower, []interface{}{set1, set2}) }
MathLogExpression <- "LOG"i ws "(" ws ex1:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" {
return createFunctionCall(parsers.FunctionCallMathLog, append([]interface{}{ex1}, others.([]interface{})...))
}
MathNumberBinExpression <- "NumberBin"i ws "(" ws ex1:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" {
return createFunctionCall(parsers.FunctionCallMathNumberBin, append([]interface{}{ex1}, others.([]interface{})...))
}
MathPiExpression <- "PI"i ws "(" ws ")" { return createFunctionCall(parsers.FunctionCallMathPi, []interface{}{}) }
MathRandExpression <- "RAND"i ws "(" ws ")" { return createFunctionCall(parsers.FunctionCallMathRand, []interface{}{}) }
InFunction <- ex1:SelectProperty ws "IN"i ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" { InFunction <- ex1:SelectProperty ws "IN"i ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" {
return createFunctionCall(parsers.FunctionCallIn, append([]interface{}{ex1, ex2}, others.([]interface{})...)) return createFunctionCall(parsers.FunctionCallIn, append([]interface{}{ex1, ex2}, others.([]interface{})...))
} }

View File

@ -0,0 +1,615 @@
package memoryexecutor
import (
"math"
"math/rand"
"github.com/pikami/cosmium/internal/logger"
"github.com/pikami/cosmium/parsers"
)
func (c memoryExecutorContext) math_Abs(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case float64:
return math.Abs(val)
case int:
if val < 0 {
return -val
}
return val
default:
logger.Debug("math_Abs - got parameters of wrong type")
return 0
}
}
func (c memoryExecutorContext) math_Acos(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Acos - got parameters of wrong type")
return nil
}
if val < -1 || val > 1 {
logger.Debug("math_Acos - value out of domain for acos")
return nil
}
return math.Acos(val) * 180 / math.Pi
}
func (c memoryExecutorContext) math_Asin(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Asin - got parameters of wrong type")
return nil
}
if val < -1 || val > 1 {
logger.Debug("math_Asin - value out of domain for acos")
return nil
}
return math.Asin(val) * 180 / math.Pi
}
func (c memoryExecutorContext) math_Atan(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Atan - got parameters of wrong type")
return nil
}
return math.Atan(val) * 180 / math.Pi
}
func (c memoryExecutorContext) math_Ceiling(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case float64:
return math.Ceil(val)
case int:
return val
default:
logger.Debug("math_Ceiling - got parameters of wrong type")
return 0
}
}
func (c memoryExecutorContext) math_Cos(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Cos - got parameters of wrong type")
return nil
}
return math.Cos(val)
}
func (c memoryExecutorContext) math_Cot(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Cot - got parameters of wrong type")
return nil
}
if val == 0 {
logger.Debug("math_Cot - cotangent undefined for zero")
return nil
}
return 1 / math.Tan(val)
}
func (c memoryExecutorContext) math_Degrees(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Degrees - got parameters of wrong type")
return nil
}
return val * (180 / math.Pi)
}
func (c memoryExecutorContext) math_Exp(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Exp - got parameters of wrong type")
return nil
}
return math.Exp(val)
}
func (c memoryExecutorContext) math_Floor(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case float64:
return math.Floor(val)
case int:
return val
default:
logger.Debug("math_Floor - got parameters of wrong type")
return 0
}
}
func (c memoryExecutorContext) math_IntBitNot(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case int:
return ^val
default:
logger.Debug("math_IntBitNot - got parameters of wrong type")
return nil
}
}
func (c memoryExecutorContext) math_Log10(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Log10 - got parameters of wrong type")
return nil
}
if val <= 0 {
logger.Debug("math_Log10 - value must be greater than 0")
return nil
}
return math.Log10(val)
}
func (c memoryExecutorContext) math_Radians(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Radians - got parameters of wrong type")
return nil
}
return val * (math.Pi / 180.0)
}
func (c memoryExecutorContext) math_Round(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case float64:
return math.Round(val)
case int:
return val
default:
logger.Debug("math_Round - got parameters of wrong type")
return nil
}
}
func (c memoryExecutorContext) math_Sign(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case float64:
if val > 0 {
return 1
} else if val < 0 {
return -1
} else {
return 0
}
case int:
if val > 0 {
return 1
} else if val < 0 {
return -1
} else {
return 0
}
default:
logger.Debug("math_Sign - got parameters of wrong type")
return nil
}
}
func (c memoryExecutorContext) math_Sin(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Sin - got parameters of wrong type")
return nil
}
return math.Sin(val)
}
func (c memoryExecutorContext) math_Sqrt(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Sqrt - got parameters of wrong type")
return nil
}
return math.Sqrt(val)
}
func (c memoryExecutorContext) math_Square(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Square - got parameters of wrong type")
return nil
}
return math.Pow(val, 2)
}
func (c memoryExecutorContext) math_Tan(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
val, valIsNumber := numToFloat64(ex)
if !valIsNumber {
logger.Debug("math_Tan - got parameters of wrong type")
return nil
}
return math.Tan(val)
}
func (c memoryExecutorContext) math_Trunc(arguments []interface{}, row RowType) interface{} {
exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row)
switch val := ex.(type) {
case float64:
return math.Trunc(val)
case int:
return float64(val)
default:
logger.Debug("math_Trunc - got parameters of wrong type")
return nil
}
}
func (c memoryExecutorContext) math_Atn2(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
y, yIsNumber := numToFloat64(ex1)
x, xIsNumber := numToFloat64(ex2)
if !yIsNumber || !xIsNumber {
logger.Debug("math_Atn2 - got parameters of wrong type")
return nil
}
return math.Atan2(y, x)
}
func (c memoryExecutorContext) math_IntAdd(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1Number, ex1IsNumber := numToInt(ex1)
ex2Number, ex2IsNumber := numToInt(ex2)
if !ex1IsNumber || !ex2IsNumber {
logger.Debug("math_IntAdd - got parameters of wrong type")
return nil
}
return ex1Number + ex2Number
}
func (c memoryExecutorContext) math_IntBitAnd(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
ex1Int, ex1IsInt := numToInt(ex1)
ex2Int, ex2IsInt := numToInt(ex2)
if !ex1IsInt || !ex2IsInt {
logger.Debug("math_IntBitAnd - got parameters of wrong type")
return nil
}
return ex1Int & ex2Int
}
func (c memoryExecutorContext) math_IntBitLeftShift(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2)
if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitLeftShift - got parameters of wrong type")
return nil
}
return num1 << uint(num2)
}
func (c memoryExecutorContext) math_IntBitOr(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitOr - got parameters of wrong type")
return nil
}
return num1 | num2
}
func (c memoryExecutorContext) math_IntBitRightShift(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2)
if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitRightShift - got parameters of wrong type")
return nil
}
return num1 >> uint(num2)
}
func (c memoryExecutorContext) math_IntBitXor(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt {
logger.Debug("math_IntBitXor - got parameters of wrong type")
return nil
}
return num1 ^ num2
}
func (c memoryExecutorContext) math_IntDiv(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt || num2 == 0 {
logger.Debug("math_IntDiv - got parameters of wrong type or divide by zero")
return nil
}
return num1 / num2
}
func (c memoryExecutorContext) math_IntMul(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt {
logger.Debug("math_IntMul - got parameters of wrong type")
return nil
}
return num1 * num2
}
func (c memoryExecutorContext) math_IntSub(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt {
logger.Debug("math_IntSub - got parameters of wrong type")
return nil
}
return num1 - num2
}
func (c memoryExecutorContext) math_IntMod(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int)
if !num1IsInt || !num2IsInt || num2 == 0 {
logger.Debug("math_IntMod - got parameters of wrong type or divide by zero")
return nil
}
return num1 % num2
}
func (c memoryExecutorContext) math_Power(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row)
ex2 := c.getFieldValue(exItem2, row)
base, baseIsNumber := numToFloat64(ex1)
exponent, exponentIsNumber := numToFloat64(ex2)
if !baseIsNumber || !exponentIsNumber {
logger.Debug("math_Power - got parameters of wrong type")
return nil
}
return math.Pow(base, exponent)
}
func (c memoryExecutorContext) math_Log(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem1, row)
var base float64 = math.E
if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem)
baseValueObject := c.getFieldValue(exItem2, row)
baseValue, baseValueIsNumber := numToFloat64(baseValueObject)
if !baseValueIsNumber {
logger.Debug("math_Log - base parameter must be a numeric value")
return nil
}
if baseValue > 0 && baseValue != 1 {
base = baseValue
} else {
logger.Debug("math_Log - base must be greater than 0 and not equal to 1")
return nil
}
}
num, numIsNumber := numToFloat64(ex)
if !numIsNumber || num <= 0 {
logger.Debug("math_Log - parameter must be a positive numeric value")
return nil
}
return math.Log(num) / math.Log(base)
}
func (c memoryExecutorContext) math_NumberBin(arguments []interface{}, row RowType) interface{} {
exItem1 := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem1, row)
binSize := 1.0
if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem)
binSizeValueObject := c.getFieldValue(exItem2, row)
binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject)
if !binSizeValueIsNumber {
logger.Debug("math_NumberBin - base parameter must be a numeric value")
return nil
}
if binSizeValue != 0 {
binSize = binSizeValue
} else {
logger.Debug("math_NumberBin - base must not be equal to 0")
return nil
}
}
num, numIsNumber := numToFloat64(ex)
if !numIsNumber {
logger.Debug("math_NumberBin - parameter must be a numeric value")
return nil
}
return math.Floor(num/binSize) * binSize
}
func (c memoryExecutorContext) math_Pi() interface{} {
return math.Pi
}
func (c memoryExecutorContext) math_Rand() interface{} {
return rand.Float64()
}
func numToInt(ex interface{}) (int, bool) {
switch val := ex.(type) {
case float64:
return int(val), true
case int:
return val, true
default:
return 0, false
}
}
func numToFloat64(num interface{}) (float64, bool) {
switch val := num.(type) {
case float64:
return val, true
case int:
return float64(val), true
default:
return 0, false
}
}

View File

@ -0,0 +1,269 @@
package memoryexecutor_test
import (
"math"
"testing"
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
)
func Test_Execute_MathFunctions(t *testing.T) {
mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": 1, "value": 0.0},
map[string]interface{}{"id": 2, "value": 1.0},
map[string]interface{}{"id": 3, "value": -1.0},
map[string]interface{}{"id": 4, "value": 0.5},
map[string]interface{}{"id": 5, "value": -0.5},
map[string]interface{}{"id": 6, "value": 0.707},
map[string]interface{}{"id": 7, "value": -0.707},
map[string]interface{}{"id": 8, "value": 0.866},
map[string]interface{}{"id": 9, "value": -0.866},
}
mockDataInts := []memoryexecutor.RowType{
map[string]interface{}{"id": 1, "value": -1},
map[string]interface{}{"id": 2, "value": 0},
map[string]interface{}{"id": 3, "value": 1},
map[string]interface{}{"id": 4, "value": 5},
}
t.Run("Should execute function ABS(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathAbs,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": 0.0},
map[string]interface{}{"value": 1.0, "result": 1.0},
map[string]interface{}{"value": -1.0, "result": 1.0},
map[string]interface{}{"value": 0.5, "result": 0.5},
map[string]interface{}{"value": -0.5, "result": 0.5},
map[string]interface{}{"value": 0.707, "result": 0.707},
map[string]interface{}{"value": -0.707, "result": 0.707},
map[string]interface{}{"value": 0.866, "result": 0.866},
map[string]interface{}{"value": -0.866, "result": 0.866},
},
)
})
t.Run("Should execute function ACOS(cosine)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathAcos,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": math.Acos(0.0) * 180 / math.Pi},
map[string]interface{}{"value": 1.0, "result": math.Acos(1.0) * 180 / math.Pi},
map[string]interface{}{"value": -1.0, "result": math.Acos(-1.0) * 180 / math.Pi},
map[string]interface{}{"value": 0.5, "result": math.Acos(0.5) * 180 / math.Pi},
map[string]interface{}{"value": -0.5, "result": math.Acos(-0.5) * 180 / math.Pi},
map[string]interface{}{"value": 0.707, "result": math.Acos(0.707) * 180 / math.Pi},
map[string]interface{}{"value": -0.707, "result": math.Acos(-0.707) * 180 / math.Pi},
map[string]interface{}{"value": 0.866, "result": math.Acos(0.866) * 180 / math.Pi},
map[string]interface{}{"value": -0.866, "result": math.Acos(-0.866) * 180 / math.Pi},
},
)
})
t.Run("Should execute function ASIN(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathAsin,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": math.Asin(0.0) * 180 / math.Pi},
map[string]interface{}{"value": 1.0, "result": math.Asin(1.0) * 180 / math.Pi},
map[string]interface{}{"value": -1.0, "result": math.Asin(-1.0) * 180 / math.Pi},
map[string]interface{}{"value": 0.5, "result": math.Asin(0.5) * 180 / math.Pi},
map[string]interface{}{"value": -0.5, "result": math.Asin(-0.5) * 180 / math.Pi},
map[string]interface{}{"value": 0.707, "result": math.Asin(0.707) * 180 / math.Pi},
map[string]interface{}{"value": -0.707, "result": math.Asin(-0.707) * 180 / math.Pi},
map[string]interface{}{"value": 0.866, "result": math.Asin(0.866) * 180 / math.Pi},
map[string]interface{}{"value": -0.866, "result": math.Asin(-0.866) * 180 / math.Pi},
},
)
})
t.Run("Should execute function ATAN(tangent)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathAtan,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": math.Atan(0.0) * 180 / math.Pi},
map[string]interface{}{"value": 1.0, "result": math.Atan(1.0) * 180 / math.Pi},
map[string]interface{}{"value": -1.0, "result": math.Atan(-1.0) * 180 / math.Pi},
map[string]interface{}{"value": 0.5, "result": math.Atan(0.5) * 180 / math.Pi},
map[string]interface{}{"value": -0.5, "result": math.Atan(-0.5) * 180 / math.Pi},
map[string]interface{}{"value": 0.707, "result": math.Atan(0.707) * 180 / math.Pi},
map[string]interface{}{"value": -0.707, "result": math.Atan(-0.707) * 180 / math.Pi},
map[string]interface{}{"value": 0.866, "result": math.Atan(0.866) * 180 / math.Pi},
map[string]interface{}{"value": -0.866, "result": math.Atan(-0.866) * 180 / math.Pi},
},
)
})
t.Run("Should execute function COS(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathCos,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": math.Cos(0.0)},
map[string]interface{}{"value": 1.0, "result": math.Cos(1.0)},
map[string]interface{}{"value": -1.0, "result": math.Cos(-1.0)},
map[string]interface{}{"value": 0.5, "result": math.Cos(0.5)},
map[string]interface{}{"value": -0.5, "result": math.Cos(-0.5)},
map[string]interface{}{"value": 0.707, "result": math.Cos(0.707)},
map[string]interface{}{"value": -0.707, "result": math.Cos(-0.707)},
map[string]interface{}{"value": 0.866, "result": math.Cos(0.866)},
map[string]interface{}{"value": -0.866, "result": math.Cos(-0.866)},
},
)
})
t.Run("Should execute function COT(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathCot,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": nil},
map[string]interface{}{"value": 1.0, "result": 1 / math.Tan(1.0)},
map[string]interface{}{"value": -1.0, "result": 1 / math.Tan(-1.0)},
map[string]interface{}{"value": 0.5, "result": 1 / math.Tan(0.5)},
map[string]interface{}{"value": -0.5, "result": 1 / math.Tan(-0.5)},
map[string]interface{}{"value": 0.707, "result": 1 / math.Tan(0.707)},
map[string]interface{}{"value": -0.707, "result": 1 / math.Tan(-0.707)},
map[string]interface{}{"value": 0.866, "result": 1 / math.Tan(0.866)},
map[string]interface{}{"value": -0.866, "result": 1 / math.Tan(-0.866)},
},
)
})
t.Run("Should execute function Degrees(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathDegrees,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": 0.0 * (180 / math.Pi)},
map[string]interface{}{"value": 1.0, "result": 1.0 * (180 / math.Pi)},
map[string]interface{}{"value": -1.0, "result": -1.0 * (180 / math.Pi)},
map[string]interface{}{"value": 0.5, "result": 0.5 * (180 / math.Pi)},
map[string]interface{}{"value": -0.5, "result": -0.5 * (180 / math.Pi)},
map[string]interface{}{"value": 0.707, "result": 0.707 * (180 / math.Pi)},
map[string]interface{}{"value": -0.707, "result": -0.707 * (180 / math.Pi)},
map[string]interface{}{"value": 0.866, "result": 0.866 * (180 / math.Pi)},
map[string]interface{}{"value": -0.866, "result": -0.866 * (180 / math.Pi)},
},
)
})
t.Run("Should execute function EXP(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathExp,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": math.Exp(0.0)},
map[string]interface{}{"value": 1.0, "result": math.Exp(1.0)},
map[string]interface{}{"value": -1.0, "result": math.Exp(-1.0)},
map[string]interface{}{"value": 0.5, "result": math.Exp(0.5)},
map[string]interface{}{"value": -0.5, "result": math.Exp(-0.5)},
map[string]interface{}{"value": 0.707, "result": math.Exp(0.707)},
map[string]interface{}{"value": -0.707, "result": math.Exp(-0.707)},
map[string]interface{}{"value": 0.866, "result": math.Exp(0.866)},
map[string]interface{}{"value": -0.866, "result": math.Exp(-0.866)},
},
)
})
t.Run("Should execute function FLOOR(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathFloor,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": math.Floor(0.0)},
map[string]interface{}{"value": 1.0, "result": math.Floor(1.0)},
map[string]interface{}{"value": -1.0, "result": math.Floor(-1.0)},
map[string]interface{}{"value": 0.5, "result": math.Floor(0.5)},
map[string]interface{}{"value": -0.5, "result": math.Floor(-0.5)},
map[string]interface{}{"value": 0.707, "result": math.Floor(0.707)},
map[string]interface{}{"value": -0.707, "result": math.Floor(-0.707)},
map[string]interface{}{"value": 0.866, "result": math.Floor(0.866)},
map[string]interface{}{"value": -0.866, "result": math.Floor(-0.866)},
},
)
})
t.Run("Should execute function IntBitNot(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathIntBitNot,
mockDataInts,
[]memoryexecutor.RowType{
map[string]interface{}{"value": -1, "result": ^-1},
map[string]interface{}{"value": 0, "result": ^0},
map[string]interface{}{"value": 1, "result": ^1},
map[string]interface{}{"value": 5, "result": ^5},
},
)
})
t.Run("Should execute function LOG10(value)", func(t *testing.T) {
testMathFunctionExecute(
t,
parsers.FunctionCallMathLog10,
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"value": 0.0, "result": nil},
map[string]interface{}{"value": 1.0, "result": math.Log10(1.0)},
map[string]interface{}{"value": -1.0, "result": nil},
map[string]interface{}{"value": 0.5, "result": math.Log10(0.5)},
map[string]interface{}{"value": -0.5, "result": nil},
map[string]interface{}{"value": 0.707, "result": math.Log10(0.707)},
map[string]interface{}{"value": -0.707, "result": nil},
map[string]interface{}{"value": 0.866, "result": math.Log10(0.866)},
map[string]interface{}{"value": -0.866, "result": nil},
},
)
})
}
func testMathFunctionExecute(
t *testing.T,
functionCallType parsers.FunctionCallType,
data []memoryexecutor.RowType,
expectedData []memoryexecutor.RowType,
) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "result",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: functionCallType,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "value"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
data,
expectedData,
)
}

View File

@ -284,6 +284,79 @@ func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row RowTy
case parsers.FunctionCallSetUnion: case parsers.FunctionCallSetUnion:
return c.set_Union(typedValue.Arguments, rowValue) return c.set_Union(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathAbs:
return c.math_Abs(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathAcos:
return c.math_Acos(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathAsin:
return c.math_Asin(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathAtan:
return c.math_Atan(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathCeiling:
return c.math_Ceiling(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathCos:
return c.math_Cos(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathCot:
return c.math_Cot(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathDegrees:
return c.math_Degrees(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathExp:
return c.math_Exp(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathFloor:
return c.math_Floor(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntBitNot:
return c.math_IntBitNot(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathLog10:
return c.math_Log10(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathRadians:
return c.math_Radians(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathRound:
return c.math_Round(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathSign:
return c.math_Sign(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathSin:
return c.math_Sin(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathSqrt:
return c.math_Sqrt(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathSquare:
return c.math_Square(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathTan:
return c.math_Tan(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathTrunc:
return c.math_Trunc(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathAtn2:
return c.math_Atn2(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntAdd:
return c.math_IntAdd(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntBitAnd:
return c.math_IntBitAnd(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntBitLeftShift:
return c.math_IntBitLeftShift(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntBitOr:
return c.math_IntBitOr(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntBitRightShift:
return c.math_IntBitRightShift(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntBitXor:
return c.math_IntBitXor(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntDiv:
return c.math_IntDiv(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntMod:
return c.math_IntMod(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntMul:
return c.math_IntMul(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathIntSub:
return c.math_IntSub(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathPower:
return c.math_Power(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathLog:
return c.math_Log(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathNumberBin:
return c.math_NumberBin(typedValue.Arguments, rowValue)
case parsers.FunctionCallMathPi:
return c.math_Pi()
case parsers.FunctionCallMathRand:
return c.math_Rand()
case parsers.FunctionCallAggregateAvg: case parsers.FunctionCallAggregateAvg:
return c.aggregate_Avg(typedValue.Arguments, row) return c.aggregate_Avg(typedValue.Arguments, row)
case parsers.FunctionCallAggregateCount: case parsers.FunctionCallAggregateCount: