From ca49228c66a42d28ac160fd655a6fb0b69e533c9 Mon Sep 17 00:00:00 2001 From: Pijus Kamandulis Date: Mon, 12 Feb 2024 00:17:21 +0200 Subject: [PATCH] Implement in-memory query executor --- parsers/nosql/nosql_test.go | 4 +- .../memory_executor/memory_executor.go | 98 +++++++++++++++++ .../memory_executor/memory_executor_test.go | 100 ++++++++++++++++++ 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 query_executors/memory_executor/memory_executor.go create mode 100644 query_executors/memory_executor/memory_executor_test.go diff --git a/parsers/nosql/nosql_test.go b/parsers/nosql/nosql_test.go index d333558..402ce8f 100644 --- a/parsers/nosql/nosql_test.go +++ b/parsers/nosql/nosql_test.go @@ -57,7 +57,7 @@ func Test_Parse(t *testing.T) { t, `select c.id FROM c - WHERE c.pk=true`, + WHERE c.isCool=true`, parsers.SelectStmt{ Columns: []parsers.FieldPath{ {Path: []string{"c", "id"}}, @@ -65,7 +65,7 @@ func Test_Parse(t *testing.T) { Table: parsers.Table{Value: "c"}, Filters: parsers.ComparisonExpression{ Operation: "=", - Left: parsers.FieldPath{Path: []string{"c", "pk"}}, + Left: parsers.FieldPath{Path: []string{"c", "isCool"}}, Right: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true}, }, }, diff --git a/query_executors/memory_executor/memory_executor.go b/query_executors/memory_executor/memory_executor.go new file mode 100644 index 0000000..eecdea0 --- /dev/null +++ b/query_executors/memory_executor/memory_executor.go @@ -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 +} diff --git a/query_executors/memory_executor/memory_executor_test.go b/query_executors/memory_executor/memory_executor_test.go new file mode 100644 index 0000000..b11f899 --- /dev/null +++ b/query_executors/memory_executor/memory_executor_test.go @@ -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"}, + }, + ) + }) +}