mirror of https://github.com/pikami/cosmium.git
Implement in-memory query executor
This commit is contained in:
parent
12510ea3fa
commit
ca49228c66
|
@ -57,7 +57,7 @@ func Test_Parse(t *testing.T) {
|
||||||
t,
|
t,
|
||||||
`select c.id
|
`select c.id
|
||||||
FROM c
|
FROM c
|
||||||
WHERE c.pk=true`,
|
WHERE c.isCool=true`,
|
||||||
parsers.SelectStmt{
|
parsers.SelectStmt{
|
||||||
Columns: []parsers.FieldPath{
|
Columns: []parsers.FieldPath{
|
||||||
{Path: []string{"c", "id"}},
|
{Path: []string{"c", "id"}},
|
||||||
|
@ -65,7 +65,7 @@ func Test_Parse(t *testing.T) {
|
||||||
Table: parsers.Table{Value: "c"},
|
Table: parsers.Table{Value: "c"},
|
||||||
Filters: parsers.ComparisonExpression{
|
Filters: parsers.ComparisonExpression{
|
||||||
Operation: "=",
|
Operation: "=",
|
||||||
Left: parsers.FieldPath{Path: []string{"c", "pk"}},
|
Left: parsers.FieldPath{Path: []string{"c", "isCool"}},
|
||||||
Right: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
|
Right: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package memoryexecutor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RowType interface{}
|
||||||
|
type ExpressionType interface{}
|
||||||
|
|
||||||
|
func Execute(query parsers.SelectStmt, data []RowType) []RowType {
|
||||||
|
var result []RowType
|
||||||
|
|
||||||
|
// Iterate over each row in the data
|
||||||
|
for _, row := range data {
|
||||||
|
// Check if the row satisfies the filter conditions
|
||||||
|
if evaluateFilters(query.Filters, row) {
|
||||||
|
// Construct a new row based on the selected columns
|
||||||
|
newRow := make(map[string]interface{})
|
||||||
|
for _, column := range query.Columns {
|
||||||
|
destinationName := column.Alias
|
||||||
|
if destinationName == "" {
|
||||||
|
destinationName = column.Path[len(column.Path)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
newRow[destinationName] = getFieldValue(column, row)
|
||||||
|
}
|
||||||
|
// Add the new row to the result
|
||||||
|
result = append(result, newRow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to evaluate filter conditions recursively
|
||||||
|
func evaluateFilters(expr ExpressionType, row RowType) bool {
|
||||||
|
if expr == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typedValue := expr.(type) {
|
||||||
|
case parsers.ComparisonExpression:
|
||||||
|
leftValue := getExpressionParameterValue(typedValue.Left, row)
|
||||||
|
rightValue := getExpressionParameterValue(typedValue.Right, row)
|
||||||
|
|
||||||
|
switch typedValue.Operation {
|
||||||
|
case "=":
|
||||||
|
return leftValue == rightValue
|
||||||
|
// Handle other comparison operators as needed
|
||||||
|
}
|
||||||
|
case parsers.LogicalExpression:
|
||||||
|
var result bool
|
||||||
|
for i, expression := range typedValue.Expressions {
|
||||||
|
expressionResult := evaluateFilters(expression, row)
|
||||||
|
if i == 0 {
|
||||||
|
result = expressionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typedValue.Operation {
|
||||||
|
case parsers.LogicalExpressionTypeAnd:
|
||||||
|
result = result && expressionResult
|
||||||
|
if !result {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case parsers.LogicalExpressionTypeOr:
|
||||||
|
result = result || expressionResult
|
||||||
|
if result {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFieldValue(field parsers.FieldPath, row RowType) interface{} {
|
||||||
|
value := row
|
||||||
|
for _, pathSegment := range field.Path[1:] {
|
||||||
|
if nestedValue, ok := value.(map[string]interface{}); ok {
|
||||||
|
value = nestedValue[pathSegment]
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExpressionParameterValue(parameter interface{}, row RowType) interface{} {
|
||||||
|
switch typedParameter := parameter.(type) {
|
||||||
|
case parsers.FieldPath:
|
||||||
|
return getFieldValue(typedParameter, row)
|
||||||
|
case parsers.Constant:
|
||||||
|
return typedParameter.Value
|
||||||
|
}
|
||||||
|
// TODO: Handle error
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package memoryexecutor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pikami/cosmium/parsers"
|
||||||
|
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testQueryExecute(
|
||||||
|
t *testing.T,
|
||||||
|
query parsers.SelectStmt,
|
||||||
|
data []memoryexecutor.RowType,
|
||||||
|
expectedData []memoryexecutor.RowType,
|
||||||
|
) {
|
||||||
|
result := memoryexecutor.Execute(query, data)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(result, expectedData) {
|
||||||
|
t.Errorf("parsed query does not match expected structure.\nExpected: %+v\nGot: %+v", expectedData, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Execute(t *testing.T) {
|
||||||
|
mockData := []memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "12345", "pk": 123, "_self": "self1", "_rid": "rid1", "_ts": 123456, "isCool": false},
|
||||||
|
map[string]interface{}{"id": "67890", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Shoul execute simple SELECT", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
Columns: []parsers.FieldPath{
|
||||||
|
{Path: []string{"c", "id"}},
|
||||||
|
{Path: []string{"c", "pk"}},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{Value: "c"},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "12345", "pk": 123},
|
||||||
|
map[string]interface{}{"id": "67890", "pk": 456},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Shoul execute SELECT with single WHERE condition", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
Columns: []parsers.FieldPath{
|
||||||
|
{Path: []string{"c", "id"}},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{Value: "c"},
|
||||||
|
Filters: parsers.ComparisonExpression{
|
||||||
|
Operation: "=",
|
||||||
|
Left: parsers.FieldPath{Path: []string{"c", "isCool"}},
|
||||||
|
Right: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "67890"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should execute SELECT with multiple WHERE conditions", func(t *testing.T) {
|
||||||
|
testQueryExecute(
|
||||||
|
t,
|
||||||
|
parsers.SelectStmt{
|
||||||
|
Columns: []parsers.FieldPath{
|
||||||
|
{Path: []string{"c", "id"}},
|
||||||
|
{Path: []string{"c", "_self"}, Alias: "self"},
|
||||||
|
},
|
||||||
|
Table: parsers.Table{Value: "c"},
|
||||||
|
Filters: parsers.LogicalExpression{
|
||||||
|
Operation: parsers.LogicalExpressionTypeAnd,
|
||||||
|
Expressions: []interface{}{
|
||||||
|
parsers.ComparisonExpression{
|
||||||
|
Operation: "=",
|
||||||
|
Left: parsers.FieldPath{Path: []string{"c", "id"}},
|
||||||
|
Right: parsers.Constant{Type: parsers.ConstantTypeString, Value: "67890"},
|
||||||
|
},
|
||||||
|
parsers.ComparisonExpression{
|
||||||
|
Operation: "=",
|
||||||
|
Left: parsers.FieldPath{Path: []string{"c", "pk"}},
|
||||||
|
Right: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 456},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mockData,
|
||||||
|
[]memoryexecutor.RowType{
|
||||||
|
map[string]interface{}{"id": "67890", "self": "self2"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue