Implement in-memory query executor

This commit is contained in:
Pijus Kamandulis 2024-02-12 00:17:21 +02:00
parent 12510ea3fa
commit ca49228c66
3 changed files with 200 additions and 2 deletions

View File

@ -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},
}, },
}, },

View File

@ -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
}

View File

@ -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"},
},
)
})
}