From 03623e5a82fdd66e239e46eb7af49eb9c435fcc6 Mon Sep 17 00:00:00 2001 From: Pijus Kamandulis Date: Fri, 16 Feb 2024 00:13:11 +0200 Subject: [PATCH] Added support for query parameters --- api/handlers/documents.go | 19 +- api/tests/documents_test.go | 29 ++- internal/repositories/documents.go | 9 +- parsers/models.go | 2 + parsers/nosql/nosql.go | 185 +++++++++++------- parsers/nosql/nosql.peg | 6 +- parsers/nosql/nosql_test.go | 16 +- .../memory_executor/memory_executor.go | 23 ++- .../memory_executor/memory_executor_test.go | 24 +++ 9 files changed, 222 insertions(+), 91 deletions(-) diff --git a/api/handlers/documents.go b/api/handlers/documents.go index 4e0f1ac..0c4647e 100644 --- a/api/handlers/documents.go +++ b/api/handlers/documents.go @@ -109,8 +109,13 @@ func DocumentsPost(c *gin.Context) { return } + var queryParameters map[string]interface{} + if paramsArray, ok := requestBody["parameters"].([]interface{}); ok { + queryParameters = parametersToMap(paramsArray) + } + // TODO: Handle these {"query":"select c.id, c._self, c._rid, c._ts, [c[\"pk\"]] as _partitionKeyValue from c"} - docs, status := repositories.ExecuteQueryDocuments(databaseId, collectionId, query.(string)) + docs, status := repositories.ExecuteQueryDocuments(databaseId, collectionId, query.(string), queryParameters) if status != repositorymodels.StatusOk { // TODO: Currently we return everything if the query fails GetAllDocuments(c) @@ -139,3 +144,15 @@ func DocumentsPost(c *gin.Context) { c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"}) } + +func parametersToMap(pairs []interface{}) map[string]interface{} { + result := make(map[string]interface{}) + + for _, pair := range pairs { + if pairMap, ok := pair.(map[string]interface{}); ok { + result[pairMap["name"].(string)] = pairMap["value"] + } + } + + return result +} diff --git a/api/tests/documents_test.go b/api/tests/documents_test.go index fd17303..d9844f5 100644 --- a/api/tests/documents_test.go +++ b/api/tests/documents_test.go @@ -13,11 +13,18 @@ import ( "github.com/stretchr/testify/assert" ) -func testCosmosQuery(t *testing.T, collectionClient *azcosmos.ContainerClient, query string, expectedData []interface{}) { +func testCosmosQuery(t *testing.T, + collectionClient *azcosmos.ContainerClient, + query string, + queryParameters []azcosmos.QueryParameter, + expectedData []interface{}, +) { pager := collectionClient.NewQueryItemsPager( query, azcosmos.PartitionKey{}, - &azcosmos.QueryOptions{}) + &azcosmos.QueryOptions{ + QueryParameters: queryParameters, + }) context := context.TODO() items := make([]interface{}, 0) @@ -70,6 +77,7 @@ func Test_Documents(t *testing.T) { t.Run("Should query document", func(t *testing.T) { testCosmosQuery(t, collectionClient, "SELECT c.id, c[\"pk\"] FROM c", + nil, []interface{}{ map[string]interface{}{"id": "12345", "pk": "123"}, map[string]interface{}{"id": "67890", "pk": "456"}, @@ -80,6 +88,7 @@ func Test_Documents(t *testing.T) { t.Run("Should query VALUE array", func(t *testing.T) { testCosmosQuery(t, collectionClient, "SELECT VALUE [c.id, c[\"pk\"]] FROM c", + nil, []interface{}{ []interface{}{"12345", "123"}, []interface{}{"67890", "456"}, @@ -90,6 +99,7 @@ func Test_Documents(t *testing.T) { t.Run("Should query VALUE object", func(t *testing.T) { testCosmosQuery(t, collectionClient, "SELECT VALUE { id: c.id, _pk: c.pk } FROM c", + nil, []interface{}{ map[string]interface{}{"id": "12345", "_pk": "123"}, map[string]interface{}{"id": "67890", "_pk": "456"}, @@ -102,6 +112,21 @@ func Test_Documents(t *testing.T) { `select c.id FROM c WHERE c.isCool=true`, + nil, + []interface{}{ + map[string]interface{}{"id": "67890"}, + }, + ) + }) + + t.Run("Should query document with query parameters", func(t *testing.T) { + testCosmosQuery(t, collectionClient, + `select c.id + FROM c + WHERE c.id=@param_id`, + []azcosmos.QueryParameter{ + {Name: "@param_id", Value: "67890"}, + }, []interface{}{ map[string]interface{}{"id": "67890"}, }, diff --git a/internal/repositories/documents.go b/internal/repositories/documents.go index 79cb44f..bfb8ba7 100644 --- a/internal/repositories/documents.go +++ b/internal/repositories/documents.go @@ -103,7 +103,7 @@ func CreateDocument(databaseId string, collectionId string, document map[string] return repositorymodels.StatusOk } -func ExecuteQueryDocuments(databaseId string, collectionId string, query string) ([]memoryexecutor.RowType, repositorymodels.RepositoryStatus) { +func ExecuteQueryDocuments(databaseId string, collectionId string, query string, queryParameters map[string]interface{}) ([]memoryexecutor.RowType, repositorymodels.RepositoryStatus) { parsedQuery, err := nosql.Parse("", []byte(query)) if err != nil { log.Printf("Failed to parse query: %s\nerr: %v", query, err) @@ -120,5 +120,10 @@ func ExecuteQueryDocuments(databaseId string, collectionId string, query string) covDocs = append(covDocs, map[string]interface{}(doc)) } - return memoryexecutor.Execute(parsedQuery.(parsers.SelectStmt), covDocs), repositorymodels.StatusOk + if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok { + typedQuery.Parameters = queryParameters + return memoryexecutor.Execute(typedQuery, covDocs), repositorymodels.StatusOk + } + + return nil, repositorymodels.BadRequest } diff --git a/parsers/models.go b/parsers/models.go index 5606cc0..4ce1b00 100644 --- a/parsers/models.go +++ b/parsers/models.go @@ -14,6 +14,7 @@ const ( ConstantTypeInteger ConstantTypeFloat ConstantTypeBoolean + ConstantTypeParameterConstant ) type SelectItemType int @@ -29,6 +30,7 @@ type SelectStmt struct { Table Table Filters interface{} Count int + Parameters map[string]interface{} } type Table struct { diff --git a/parsers/nosql/nosql.go b/parsers/nosql/nosql.go index 2e5cba1..39e5519 100644 --- a/parsers/nosql/nosql.go +++ b/parsers/nosql/nosql.go @@ -1197,20 +1197,47 @@ var g = &grammar{ pos: position{line: 208, col: 60, offset: 5787}, name: "BooleanLiteral", }, + &ruleRefExpr{ + pos: position{line: 208, col: 77, offset: 5804}, + name: "ParameterConstant", + }, + }, + }, + }, + { + name: "ParameterConstant", + pos: position{line: 210, col: 1, offset: 5823}, + expr: &actionExpr{ + pos: position{line: 210, col: 22, offset: 5844}, + run: (*parser).callonParameterConstant1, + expr: &seqExpr{ + pos: position{line: 210, col: 22, offset: 5844}, + exprs: []any{ + &litMatcher{ + pos: position{line: 210, col: 22, offset: 5844}, + val: "@", + ignoreCase: false, + want: "\"@\"", + }, + &ruleRefExpr{ + pos: position{line: 210, col: 26, offset: 5848}, + name: "Identifier", + }, + }, }, }, }, { name: "IntegerLiteral", - pos: position{line: 210, col: 1, offset: 5803}, + pos: position{line: 214, col: 1, offset: 5965}, expr: &actionExpr{ - pos: position{line: 210, col: 19, offset: 5821}, + pos: position{line: 214, col: 19, offset: 5983}, run: (*parser).callonIntegerLiteral1, expr: &labeledExpr{ - pos: position{line: 210, col: 19, offset: 5821}, + pos: position{line: 214, col: 19, offset: 5983}, label: "number", expr: &ruleRefExpr{ - pos: position{line: 210, col: 26, offset: 5828}, + pos: position{line: 214, col: 26, offset: 5990}, name: "Integer", }, }, @@ -1218,32 +1245,32 @@ var g = &grammar{ }, { name: "StringLiteral", - pos: position{line: 213, col: 1, offset: 5929}, + pos: position{line: 217, col: 1, offset: 6091}, expr: &actionExpr{ - pos: position{line: 213, col: 18, offset: 5946}, + pos: position{line: 217, col: 18, offset: 6108}, run: (*parser).callonStringLiteral1, expr: &seqExpr{ - pos: position{line: 213, col: 18, offset: 5946}, + pos: position{line: 217, col: 18, offset: 6108}, exprs: []any{ &litMatcher{ - pos: position{line: 213, col: 18, offset: 5946}, + pos: position{line: 217, col: 18, offset: 6108}, val: "\"", ignoreCase: false, want: "\"\\\"\"", }, &labeledExpr{ - pos: position{line: 213, col: 23, offset: 5951}, + pos: position{line: 217, col: 23, offset: 6113}, label: "chars", expr: &zeroOrMoreExpr{ - pos: position{line: 213, col: 29, offset: 5957}, + pos: position{line: 217, col: 29, offset: 6119}, expr: &ruleRefExpr{ - pos: position{line: 213, col: 29, offset: 5957}, + pos: position{line: 217, col: 29, offset: 6119}, name: "StringCharacter", }, }, }, &litMatcher{ - pos: position{line: 213, col: 46, offset: 5974}, + pos: position{line: 217, col: 46, offset: 6136}, val: "\"", ignoreCase: false, want: "\"\\\"\"", @@ -1254,17 +1281,17 @@ var g = &grammar{ }, { name: "FloatLiteral", - pos: position{line: 216, col: 1, offset: 6092}, + pos: position{line: 220, col: 1, offset: 6254}, expr: &actionExpr{ - pos: position{line: 216, col: 17, offset: 6108}, + pos: position{line: 220, col: 17, offset: 6270}, run: (*parser).callonFloatLiteral1, expr: &seqExpr{ - pos: position{line: 216, col: 17, offset: 6108}, + pos: position{line: 220, col: 17, offset: 6270}, exprs: []any{ &oneOrMoreExpr{ - pos: position{line: 216, col: 17, offset: 6108}, + pos: position{line: 220, col: 17, offset: 6270}, expr: &charClassMatcher{ - pos: position{line: 216, col: 17, offset: 6108}, + pos: position{line: 220, col: 17, offset: 6270}, val: "[0-9]", ranges: []rune{'0', '9'}, ignoreCase: false, @@ -1272,15 +1299,15 @@ var g = &grammar{ }, }, &litMatcher{ - pos: position{line: 216, col: 23, offset: 6114}, + pos: position{line: 220, col: 23, offset: 6276}, val: ".", ignoreCase: false, want: "\".\"", }, &oneOrMoreExpr{ - pos: position{line: 216, col: 26, offset: 6117}, + pos: position{line: 220, col: 26, offset: 6279}, expr: &charClassMatcher{ - pos: position{line: 216, col: 26, offset: 6117}, + pos: position{line: 220, col: 26, offset: 6279}, val: "[0-9]", ranges: []rune{'0', '9'}, ignoreCase: false, @@ -1293,21 +1320,21 @@ var g = &grammar{ }, { name: "BooleanLiteral", - pos: position{line: 220, col: 1, offset: 6273}, + pos: position{line: 224, col: 1, offset: 6435}, expr: &actionExpr{ - pos: position{line: 220, col: 19, offset: 6291}, + pos: position{line: 224, col: 19, offset: 6453}, run: (*parser).callonBooleanLiteral1, expr: &choiceExpr{ - pos: position{line: 220, col: 20, offset: 6292}, + pos: position{line: 224, col: 20, offset: 6454}, alternatives: []any{ &litMatcher{ - pos: position{line: 220, col: 20, offset: 6292}, + pos: position{line: 224, col: 20, offset: 6454}, val: "true", ignoreCase: false, want: "\"true\"", }, &litMatcher{ - pos: position{line: 220, col: 29, offset: 6301}, + pos: position{line: 224, col: 29, offset: 6463}, val: "false", ignoreCase: false, want: "\"false\"", @@ -1318,14 +1345,14 @@ var g = &grammar{ }, { name: "Integer", - pos: position{line: 225, col: 1, offset: 6455}, + pos: position{line: 229, col: 1, offset: 6617}, expr: &actionExpr{ - pos: position{line: 225, col: 12, offset: 6466}, + pos: position{line: 229, col: 12, offset: 6628}, run: (*parser).callonInteger1, expr: &oneOrMoreExpr{ - pos: position{line: 225, col: 12, offset: 6466}, + pos: position{line: 229, col: 12, offset: 6628}, expr: &charClassMatcher{ - pos: position{line: 225, col: 12, offset: 6466}, + pos: position{line: 229, col: 12, offset: 6628}, val: "[0-9]", ranges: []rune{'0', '9'}, ignoreCase: false, @@ -1336,29 +1363,29 @@ var g = &grammar{ }, { name: "StringCharacter", - pos: position{line: 229, col: 1, offset: 6518}, + pos: position{line: 233, col: 1, offset: 6680}, expr: &choiceExpr{ - pos: position{line: 229, col: 20, offset: 6537}, + pos: position{line: 233, col: 20, offset: 6699}, alternatives: []any{ &actionExpr{ - pos: position{line: 229, col: 20, offset: 6537}, + pos: position{line: 233, col: 20, offset: 6699}, run: (*parser).callonStringCharacter2, expr: &seqExpr{ - pos: position{line: 229, col: 20, offset: 6537}, + pos: position{line: 233, col: 20, offset: 6699}, exprs: []any{ ¬Expr{ - pos: position{line: 229, col: 20, offset: 6537}, + pos: position{line: 233, col: 20, offset: 6699}, expr: &choiceExpr{ - pos: position{line: 229, col: 22, offset: 6539}, + pos: position{line: 233, col: 22, offset: 6701}, alternatives: []any{ &litMatcher{ - pos: position{line: 229, col: 22, offset: 6539}, + pos: position{line: 233, col: 22, offset: 6701}, val: "\"", ignoreCase: false, want: "\"\\\"\"", }, &litMatcher{ - pos: position{line: 229, col: 28, offset: 6545}, + pos: position{line: 233, col: 28, offset: 6707}, val: "\\", ignoreCase: false, want: "\"\\\\\"", @@ -1367,28 +1394,28 @@ var g = &grammar{ }, }, &anyMatcher{ - line: 229, col: 34, offset: 6551, + line: 233, col: 34, offset: 6713, }, }, }, }, &actionExpr{ - pos: position{line: 230, col: 5, offset: 6588}, + pos: position{line: 234, col: 5, offset: 6750}, run: (*parser).callonStringCharacter9, expr: &seqExpr{ - pos: position{line: 230, col: 5, offset: 6588}, + pos: position{line: 234, col: 5, offset: 6750}, exprs: []any{ &litMatcher{ - pos: position{line: 230, col: 5, offset: 6588}, + pos: position{line: 234, col: 5, offset: 6750}, val: "\\", ignoreCase: false, want: "\"\\\\\"", }, &labeledExpr{ - pos: position{line: 230, col: 10, offset: 6593}, + pos: position{line: 234, col: 10, offset: 6755}, label: "seq", expr: &ruleRefExpr{ - pos: position{line: 230, col: 14, offset: 6597}, + pos: position{line: 234, col: 14, offset: 6759}, name: "EscapeSequenceCharacter", }, }, @@ -1400,85 +1427,85 @@ var g = &grammar{ }, { name: "EscapeSequenceCharacter", - pos: position{line: 232, col: 1, offset: 6642}, + pos: position{line: 236, col: 1, offset: 6804}, expr: &labeledExpr{ - pos: position{line: 232, col: 28, offset: 6669}, + pos: position{line: 236, col: 28, offset: 6831}, label: "char", expr: &ruleRefExpr{ - pos: position{line: 232, col: 33, offset: 6674}, + pos: position{line: 236, col: 33, offset: 6836}, name: "EscapeCharacter", }, }, }, { name: "EscapeCharacter", - pos: position{line: 234, col: 1, offset: 6691}, + pos: position{line: 238, col: 1, offset: 6853}, expr: &choiceExpr{ - pos: position{line: 234, col: 20, offset: 6710}, + pos: position{line: 238, col: 20, offset: 6872}, alternatives: []any{ &litMatcher{ - pos: position{line: 234, col: 20, offset: 6710}, + pos: position{line: 238, col: 20, offset: 6872}, val: "'", ignoreCase: false, want: "\"'\"", }, &litMatcher{ - pos: position{line: 235, col: 5, offset: 6718}, + pos: position{line: 239, col: 5, offset: 6880}, val: "\"", ignoreCase: false, want: "\"\\\"\"", }, &litMatcher{ - pos: position{line: 236, col: 5, offset: 6726}, + pos: position{line: 240, col: 5, offset: 6888}, val: "\\", ignoreCase: false, want: "\"\\\\\"", }, &actionExpr{ - pos: position{line: 237, col: 5, offset: 6735}, + pos: position{line: 241, col: 5, offset: 6897}, run: (*parser).callonEscapeCharacter5, expr: &litMatcher{ - pos: position{line: 237, col: 5, offset: 6735}, + pos: position{line: 241, col: 5, offset: 6897}, val: "b", ignoreCase: false, want: "\"b\"", }, }, &actionExpr{ - pos: position{line: 238, col: 5, offset: 6764}, + pos: position{line: 242, col: 5, offset: 6926}, run: (*parser).callonEscapeCharacter7, expr: &litMatcher{ - pos: position{line: 238, col: 5, offset: 6764}, + pos: position{line: 242, col: 5, offset: 6926}, val: "f", ignoreCase: false, want: "\"f\"", }, }, &actionExpr{ - pos: position{line: 239, col: 5, offset: 6793}, + pos: position{line: 243, col: 5, offset: 6955}, run: (*parser).callonEscapeCharacter9, expr: &litMatcher{ - pos: position{line: 239, col: 5, offset: 6793}, + pos: position{line: 243, col: 5, offset: 6955}, val: "n", ignoreCase: false, want: "\"n\"", }, }, &actionExpr{ - pos: position{line: 240, col: 5, offset: 6822}, + pos: position{line: 244, col: 5, offset: 6984}, run: (*parser).callonEscapeCharacter11, expr: &litMatcher{ - pos: position{line: 240, col: 5, offset: 6822}, + pos: position{line: 244, col: 5, offset: 6984}, val: "r", ignoreCase: false, want: "\"r\"", }, }, &actionExpr{ - pos: position{line: 241, col: 5, offset: 6851}, + pos: position{line: 245, col: 5, offset: 7013}, run: (*parser).callonEscapeCharacter13, expr: &litMatcher{ - pos: position{line: 241, col: 5, offset: 6851}, + pos: position{line: 245, col: 5, offset: 7013}, val: "t", ignoreCase: false, want: "\"t\"", @@ -1489,25 +1516,25 @@ var g = &grammar{ }, { name: "non_escape_character", - pos: position{line: 243, col: 1, offset: 6877}, + pos: position{line: 247, col: 1, offset: 7039}, expr: &actionExpr{ - pos: position{line: 243, col: 25, offset: 6901}, + pos: position{line: 247, col: 25, offset: 7063}, run: (*parser).callonnon_escape_character1, expr: &seqExpr{ - pos: position{line: 243, col: 25, offset: 6901}, + pos: position{line: 247, col: 25, offset: 7063}, exprs: []any{ ¬Expr{ - pos: position{line: 243, col: 25, offset: 6901}, + pos: position{line: 247, col: 25, offset: 7063}, expr: &ruleRefExpr{ - pos: position{line: 243, col: 27, offset: 6903}, + pos: position{line: 247, col: 27, offset: 7065}, name: "escape_character", }, }, &labeledExpr{ - pos: position{line: 243, col: 45, offset: 6921}, + pos: position{line: 247, col: 45, offset: 7083}, label: "char", expr: &anyMatcher{ - line: 243, col: 50, offset: 6926, + line: 247, col: 50, offset: 7088, }, }, }, @@ -1516,11 +1543,11 @@ var g = &grammar{ }, { name: "ws", - pos: position{line: 246, col: 1, offset: 6965}, + pos: position{line: 250, col: 1, offset: 7127}, expr: &zeroOrMoreExpr{ - pos: position{line: 246, col: 7, offset: 6971}, + pos: position{line: 250, col: 7, offset: 7133}, expr: &charClassMatcher{ - pos: position{line: 246, col: 7, offset: 6971}, + pos: position{line: 250, col: 7, offset: 7133}, val: "[ \\t\\n\\r]", chars: []rune{' ', '\t', '\n', '\r'}, ignoreCase: false, @@ -1530,11 +1557,11 @@ var g = &grammar{ }, { name: "EOF", - pos: position{line: 248, col: 1, offset: 6983}, + pos: position{line: 252, col: 1, offset: 7145}, expr: ¬Expr{ - pos: position{line: 248, col: 8, offset: 6990}, + pos: position{line: 252, col: 8, offset: 7152}, expr: &anyMatcher{ - line: 248, col: 9, offset: 6991, + line: 252, col: 9, offset: 7153, }, }, }, @@ -1821,6 +1848,16 @@ func (p *parser) callonComparisonOperator7() (any, error) { return p.cur.onComparisonOperator7() } +func (c *current) onParameterConstant1() (any, error) { + return parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: string(c.text)}, nil +} + +func (p *parser) callonParameterConstant1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onParameterConstant1() +} + func (c *current) onIntegerLiteral1(number any) (any, error) { return parsers.Constant{Type: parsers.ConstantTypeInteger, Value: number.(int)}, nil } diff --git a/parsers/nosql/nosql.peg b/parsers/nosql/nosql.peg index 4674dc5..67266b7 100644 --- a/parsers/nosql/nosql.peg +++ b/parsers/nosql/nosql.peg @@ -205,7 +205,11 @@ ComparisonOperator <- "=" / "!=" / "<" / "<=" / ">" / ">=" { return string(c.text), nil } -Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral +Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant + +ParameterConstant <- "@" Identifier { + return parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: string(c.text)}, nil +} IntegerLiteral <- number:Integer { return parsers.Constant{Type: parsers.ConstantTypeInteger, Value: number.(int)}, nil diff --git a/parsers/nosql/nosql_test.go b/parsers/nosql/nosql_test.go index c1d89ac..dd784e5 100644 --- a/parsers/nosql/nosql_test.go +++ b/parsers/nosql/nosql_test.go @@ -233,7 +233,8 @@ func Test_Parse(t *testing.T) { WHERE c.boolean=true AND c.integer=1 AND c.float=6.9 - AND c.string="hello"`, + AND c.string="hello" + AND c.param=@param_id1`, parsers.SelectStmt{ SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}}, Table: parsers.Table{Value: "c"}, @@ -241,22 +242,27 @@ func Test_Parse(t *testing.T) { Expressions: []interface{}{ parsers.ComparisonExpression{ Left: parsers.SelectItem{Path: []string{"c", "boolean"}}, - Right: parsers.Constant{Type: 3, Value: true}, + Right: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true}, Operation: "=", }, parsers.ComparisonExpression{ Left: parsers.SelectItem{Path: []string{"c", "integer"}}, - Right: parsers.Constant{Type: 1, Value: 1}, + Right: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 1}, Operation: "=", }, parsers.ComparisonExpression{ Left: parsers.SelectItem{Path: []string{"c", "float"}}, - Right: parsers.Constant{Type: 2, Value: 6.9}, + Right: parsers.Constant{Type: parsers.ConstantTypeFloat, Value: 6.9}, Operation: "=", }, parsers.ComparisonExpression{ Left: parsers.SelectItem{Path: []string{"c", "string"}}, - Right: parsers.Constant{Type: 0, Value: "hello"}, + Right: parsers.Constant{Type: parsers.ConstantTypeString, Value: "hello"}, + Operation: "=", + }, + parsers.ComparisonExpression{ + Left: parsers.SelectItem{Path: []string{"c", "param"}}, + Right: parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: "@param_id1"}, Operation: "=", }, }, diff --git a/query_executors/memory_executor/memory_executor.go b/query_executors/memory_executor/memory_executor.go index 7f83b05..c7501af 100644 --- a/query_executors/memory_executor/memory_executor.go +++ b/query_executors/memory_executor/memory_executor.go @@ -13,7 +13,7 @@ func Execute(query parsers.SelectStmt, data []RowType) []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) { + if evaluateFilters(query.Filters, query.Parameters, row) { result = append(result, selectRow(query.SelectItems, row)) } } @@ -53,15 +53,15 @@ func selectRow(selectItems []parsers.SelectItem, row RowType) interface{} { } // Helper function to evaluate filter conditions recursively -func evaluateFilters(expr ExpressionType, row RowType) bool { +func evaluateFilters(expr ExpressionType, Parameters map[string]interface{}, 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) + leftValue := getExpressionParameterValue(typedValue.Left, Parameters, row) + rightValue := getExpressionParameterValue(typedValue.Right, Parameters, row) switch typedValue.Operation { case "=": @@ -71,7 +71,7 @@ func evaluateFilters(expr ExpressionType, row RowType) bool { case parsers.LogicalExpression: var result bool for i, expression := range typedValue.Expressions { - expressionResult := evaluateFilters(expression, row) + expressionResult := evaluateFilters(expression, Parameters, row) if i == 0 { result = expressionResult } @@ -124,11 +124,22 @@ func getFieldValue(field parsers.SelectItem, row RowType) interface{} { return value } -func getExpressionParameterValue(parameter interface{}, row RowType) interface{} { +func getExpressionParameterValue( + parameter interface{}, + Parameters map[string]interface{}, + row RowType, +) interface{} { switch typedParameter := parameter.(type) { case parsers.SelectItem: return getFieldValue(typedParameter, row) case parsers.Constant: + if typedParameter.Type == parsers.ConstantTypeParameterConstant && + Parameters != nil { + if key, ok := typedParameter.Value.(string); ok { + return Parameters[key] + } + } + return typedParameter.Value } // TODO: Handle error diff --git a/query_executors/memory_executor/memory_executor_test.go b/query_executors/memory_executor/memory_executor_test.go index c18c3f3..6c4f0b9 100644 --- a/query_executors/memory_executor/memory_executor_test.go +++ b/query_executors/memory_executor/memory_executor_test.go @@ -175,6 +175,30 @@ func Test_Execute(t *testing.T) { ) }) + t.Run("Should execute SELECT with WHERE condition with defined parameter constant", func(t *testing.T) { + testQueryExecute( + t, + parsers.SelectStmt{ + SelectItems: []parsers.SelectItem{ + {Path: []string{"c", "id"}}, + }, + Table: parsers.Table{Value: "c"}, + Filters: parsers.ComparisonExpression{ + Operation: "=", + Left: parsers.SelectItem{Path: []string{"c", "id"}}, + Right: parsers.Constant{Type: parsers.ConstantTypeParameterConstant, Value: "@param_id"}, + }, + Parameters: map[string]interface{}{ + "@param_id": "456", + }, + }, + mockData, + []memoryexecutor.RowType{ + map[string]interface{}{"id": "456"}, + }, + ) + }) + t.Run("Should execute SELECT with multiple WHERE conditions", func(t *testing.T) { testQueryExecute( t,