Added support for query parameters

This commit is contained in:
Pijus Kamandulis 2024-02-16 00:13:11 +02:00
parent f183f308fb
commit 03623e5a82
9 changed files with 222 additions and 91 deletions

View File

@ -109,8 +109,13 @@ func DocumentsPost(c *gin.Context) {
return 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"} // 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 { if status != repositorymodels.StatusOk {
// TODO: Currently we return everything if the query fails // TODO: Currently we return everything if the query fails
GetAllDocuments(c) GetAllDocuments(c)
@ -139,3 +144,15 @@ func DocumentsPost(c *gin.Context) {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unknown error"}) 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
}

View File

@ -13,11 +13,18 @@ import (
"github.com/stretchr/testify/assert" "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( pager := collectionClient.NewQueryItemsPager(
query, query,
azcosmos.PartitionKey{}, azcosmos.PartitionKey{},
&azcosmos.QueryOptions{}) &azcosmos.QueryOptions{
QueryParameters: queryParameters,
})
context := context.TODO() context := context.TODO()
items := make([]interface{}, 0) items := make([]interface{}, 0)
@ -70,6 +77,7 @@ func Test_Documents(t *testing.T) {
t.Run("Should query document", func(t *testing.T) { t.Run("Should query document", func(t *testing.T) {
testCosmosQuery(t, collectionClient, testCosmosQuery(t, collectionClient,
"SELECT c.id, c[\"pk\"] FROM c", "SELECT c.id, c[\"pk\"] FROM c",
nil,
[]interface{}{ []interface{}{
map[string]interface{}{"id": "12345", "pk": "123"}, map[string]interface{}{"id": "12345", "pk": "123"},
map[string]interface{}{"id": "67890", "pk": "456"}, 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) { t.Run("Should query VALUE array", func(t *testing.T) {
testCosmosQuery(t, collectionClient, testCosmosQuery(t, collectionClient,
"SELECT VALUE [c.id, c[\"pk\"]] FROM c", "SELECT VALUE [c.id, c[\"pk\"]] FROM c",
nil,
[]interface{}{ []interface{}{
[]interface{}{"12345", "123"}, []interface{}{"12345", "123"},
[]interface{}{"67890", "456"}, []interface{}{"67890", "456"},
@ -90,6 +99,7 @@ func Test_Documents(t *testing.T) {
t.Run("Should query VALUE object", func(t *testing.T) { t.Run("Should query VALUE object", func(t *testing.T) {
testCosmosQuery(t, collectionClient, testCosmosQuery(t, collectionClient,
"SELECT VALUE { id: c.id, _pk: c.pk } FROM c", "SELECT VALUE { id: c.id, _pk: c.pk } FROM c",
nil,
[]interface{}{ []interface{}{
map[string]interface{}{"id": "12345", "_pk": "123"}, map[string]interface{}{"id": "12345", "_pk": "123"},
map[string]interface{}{"id": "67890", "_pk": "456"}, map[string]interface{}{"id": "67890", "_pk": "456"},
@ -102,6 +112,21 @@ func Test_Documents(t *testing.T) {
`select c.id `select c.id
FROM c FROM c
WHERE c.isCool=true`, 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{}{ []interface{}{
map[string]interface{}{"id": "67890"}, map[string]interface{}{"id": "67890"},
}, },

View File

@ -103,7 +103,7 @@ func CreateDocument(databaseId string, collectionId string, document map[string]
return repositorymodels.StatusOk 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)) parsedQuery, err := nosql.Parse("", []byte(query))
if err != nil { if err != nil {
log.Printf("Failed to parse query: %s\nerr: %v", query, err) 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)) 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
} }

View File

@ -14,6 +14,7 @@ const (
ConstantTypeInteger ConstantTypeInteger
ConstantTypeFloat ConstantTypeFloat
ConstantTypeBoolean ConstantTypeBoolean
ConstantTypeParameterConstant
) )
type SelectItemType int type SelectItemType int
@ -29,6 +30,7 @@ type SelectStmt struct {
Table Table Table Table
Filters interface{} Filters interface{}
Count int Count int
Parameters map[string]interface{}
} }
type Table struct { type Table struct {

View File

@ -1197,20 +1197,47 @@ var g = &grammar{
pos: position{line: 208, col: 60, offset: 5787}, pos: position{line: 208, col: 60, offset: 5787},
name: "BooleanLiteral", 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", name: "IntegerLiteral",
pos: position{line: 210, col: 1, offset: 5803}, pos: position{line: 214, col: 1, offset: 5965},
expr: &actionExpr{ expr: &actionExpr{
pos: position{line: 210, col: 19, offset: 5821}, pos: position{line: 214, col: 19, offset: 5983},
run: (*parser).callonIntegerLiteral1, run: (*parser).callonIntegerLiteral1,
expr: &labeledExpr{ expr: &labeledExpr{
pos: position{line: 210, col: 19, offset: 5821}, pos: position{line: 214, col: 19, offset: 5983},
label: "number", label: "number",
expr: &ruleRefExpr{ expr: &ruleRefExpr{
pos: position{line: 210, col: 26, offset: 5828}, pos: position{line: 214, col: 26, offset: 5990},
name: "Integer", name: "Integer",
}, },
}, },
@ -1218,32 +1245,32 @@ var g = &grammar{
}, },
{ {
name: "StringLiteral", name: "StringLiteral",
pos: position{line: 213, col: 1, offset: 5929}, pos: position{line: 217, col: 1, offset: 6091},
expr: &actionExpr{ expr: &actionExpr{
pos: position{line: 213, col: 18, offset: 5946}, pos: position{line: 217, col: 18, offset: 6108},
run: (*parser).callonStringLiteral1, run: (*parser).callonStringLiteral1,
expr: &seqExpr{ expr: &seqExpr{
pos: position{line: 213, col: 18, offset: 5946}, pos: position{line: 217, col: 18, offset: 6108},
exprs: []any{ exprs: []any{
&litMatcher{ &litMatcher{
pos: position{line: 213, col: 18, offset: 5946}, pos: position{line: 217, col: 18, offset: 6108},
val: "\"", val: "\"",
ignoreCase: false, ignoreCase: false,
want: "\"\\\"\"", want: "\"\\\"\"",
}, },
&labeledExpr{ &labeledExpr{
pos: position{line: 213, col: 23, offset: 5951}, pos: position{line: 217, col: 23, offset: 6113},
label: "chars", label: "chars",
expr: &zeroOrMoreExpr{ expr: &zeroOrMoreExpr{
pos: position{line: 213, col: 29, offset: 5957}, pos: position{line: 217, col: 29, offset: 6119},
expr: &ruleRefExpr{ expr: &ruleRefExpr{
pos: position{line: 213, col: 29, offset: 5957}, pos: position{line: 217, col: 29, offset: 6119},
name: "StringCharacter", name: "StringCharacter",
}, },
}, },
}, },
&litMatcher{ &litMatcher{
pos: position{line: 213, col: 46, offset: 5974}, pos: position{line: 217, col: 46, offset: 6136},
val: "\"", val: "\"",
ignoreCase: false, ignoreCase: false,
want: "\"\\\"\"", want: "\"\\\"\"",
@ -1254,17 +1281,17 @@ var g = &grammar{
}, },
{ {
name: "FloatLiteral", name: "FloatLiteral",
pos: position{line: 216, col: 1, offset: 6092}, pos: position{line: 220, col: 1, offset: 6254},
expr: &actionExpr{ expr: &actionExpr{
pos: position{line: 216, col: 17, offset: 6108}, pos: position{line: 220, col: 17, offset: 6270},
run: (*parser).callonFloatLiteral1, run: (*parser).callonFloatLiteral1,
expr: &seqExpr{ expr: &seqExpr{
pos: position{line: 216, col: 17, offset: 6108}, pos: position{line: 220, col: 17, offset: 6270},
exprs: []any{ exprs: []any{
&oneOrMoreExpr{ &oneOrMoreExpr{
pos: position{line: 216, col: 17, offset: 6108}, pos: position{line: 220, col: 17, offset: 6270},
expr: &charClassMatcher{ expr: &charClassMatcher{
pos: position{line: 216, col: 17, offset: 6108}, pos: position{line: 220, col: 17, offset: 6270},
val: "[0-9]", val: "[0-9]",
ranges: []rune{'0', '9'}, ranges: []rune{'0', '9'},
ignoreCase: false, ignoreCase: false,
@ -1272,15 +1299,15 @@ var g = &grammar{
}, },
}, },
&litMatcher{ &litMatcher{
pos: position{line: 216, col: 23, offset: 6114}, pos: position{line: 220, col: 23, offset: 6276},
val: ".", val: ".",
ignoreCase: false, ignoreCase: false,
want: "\".\"", want: "\".\"",
}, },
&oneOrMoreExpr{ &oneOrMoreExpr{
pos: position{line: 216, col: 26, offset: 6117}, pos: position{line: 220, col: 26, offset: 6279},
expr: &charClassMatcher{ expr: &charClassMatcher{
pos: position{line: 216, col: 26, offset: 6117}, pos: position{line: 220, col: 26, offset: 6279},
val: "[0-9]", val: "[0-9]",
ranges: []rune{'0', '9'}, ranges: []rune{'0', '9'},
ignoreCase: false, ignoreCase: false,
@ -1293,21 +1320,21 @@ var g = &grammar{
}, },
{ {
name: "BooleanLiteral", name: "BooleanLiteral",
pos: position{line: 220, col: 1, offset: 6273}, pos: position{line: 224, col: 1, offset: 6435},
expr: &actionExpr{ expr: &actionExpr{
pos: position{line: 220, col: 19, offset: 6291}, pos: position{line: 224, col: 19, offset: 6453},
run: (*parser).callonBooleanLiteral1, run: (*parser).callonBooleanLiteral1,
expr: &choiceExpr{ expr: &choiceExpr{
pos: position{line: 220, col: 20, offset: 6292}, pos: position{line: 224, col: 20, offset: 6454},
alternatives: []any{ alternatives: []any{
&litMatcher{ &litMatcher{
pos: position{line: 220, col: 20, offset: 6292}, pos: position{line: 224, col: 20, offset: 6454},
val: "true", val: "true",
ignoreCase: false, ignoreCase: false,
want: "\"true\"", want: "\"true\"",
}, },
&litMatcher{ &litMatcher{
pos: position{line: 220, col: 29, offset: 6301}, pos: position{line: 224, col: 29, offset: 6463},
val: "false", val: "false",
ignoreCase: false, ignoreCase: false,
want: "\"false\"", want: "\"false\"",
@ -1318,14 +1345,14 @@ var g = &grammar{
}, },
{ {
name: "Integer", name: "Integer",
pos: position{line: 225, col: 1, offset: 6455}, pos: position{line: 229, col: 1, offset: 6617},
expr: &actionExpr{ expr: &actionExpr{
pos: position{line: 225, col: 12, offset: 6466}, pos: position{line: 229, col: 12, offset: 6628},
run: (*parser).callonInteger1, run: (*parser).callonInteger1,
expr: &oneOrMoreExpr{ expr: &oneOrMoreExpr{
pos: position{line: 225, col: 12, offset: 6466}, pos: position{line: 229, col: 12, offset: 6628},
expr: &charClassMatcher{ expr: &charClassMatcher{
pos: position{line: 225, col: 12, offset: 6466}, pos: position{line: 229, col: 12, offset: 6628},
val: "[0-9]", val: "[0-9]",
ranges: []rune{'0', '9'}, ranges: []rune{'0', '9'},
ignoreCase: false, ignoreCase: false,
@ -1336,29 +1363,29 @@ var g = &grammar{
}, },
{ {
name: "StringCharacter", name: "StringCharacter",
pos: position{line: 229, col: 1, offset: 6518}, pos: position{line: 233, col: 1, offset: 6680},
expr: &choiceExpr{ expr: &choiceExpr{
pos: position{line: 229, col: 20, offset: 6537}, pos: position{line: 233, col: 20, offset: 6699},
alternatives: []any{ alternatives: []any{
&actionExpr{ &actionExpr{
pos: position{line: 229, col: 20, offset: 6537}, pos: position{line: 233, col: 20, offset: 6699},
run: (*parser).callonStringCharacter2, run: (*parser).callonStringCharacter2,
expr: &seqExpr{ expr: &seqExpr{
pos: position{line: 229, col: 20, offset: 6537}, pos: position{line: 233, col: 20, offset: 6699},
exprs: []any{ exprs: []any{
&notExpr{ &notExpr{
pos: position{line: 229, col: 20, offset: 6537}, pos: position{line: 233, col: 20, offset: 6699},
expr: &choiceExpr{ expr: &choiceExpr{
pos: position{line: 229, col: 22, offset: 6539}, pos: position{line: 233, col: 22, offset: 6701},
alternatives: []any{ alternatives: []any{
&litMatcher{ &litMatcher{
pos: position{line: 229, col: 22, offset: 6539}, pos: position{line: 233, col: 22, offset: 6701},
val: "\"", val: "\"",
ignoreCase: false, ignoreCase: false,
want: "\"\\\"\"", want: "\"\\\"\"",
}, },
&litMatcher{ &litMatcher{
pos: position{line: 229, col: 28, offset: 6545}, pos: position{line: 233, col: 28, offset: 6707},
val: "\\", val: "\\",
ignoreCase: false, ignoreCase: false,
want: "\"\\\\\"", want: "\"\\\\\"",
@ -1367,28 +1394,28 @@ var g = &grammar{
}, },
}, },
&anyMatcher{ &anyMatcher{
line: 229, col: 34, offset: 6551, line: 233, col: 34, offset: 6713,
}, },
}, },
}, },
}, },
&actionExpr{ &actionExpr{
pos: position{line: 230, col: 5, offset: 6588}, pos: position{line: 234, col: 5, offset: 6750},
run: (*parser).callonStringCharacter9, run: (*parser).callonStringCharacter9,
expr: &seqExpr{ expr: &seqExpr{
pos: position{line: 230, col: 5, offset: 6588}, pos: position{line: 234, col: 5, offset: 6750},
exprs: []any{ exprs: []any{
&litMatcher{ &litMatcher{
pos: position{line: 230, col: 5, offset: 6588}, pos: position{line: 234, col: 5, offset: 6750},
val: "\\", val: "\\",
ignoreCase: false, ignoreCase: false,
want: "\"\\\\\"", want: "\"\\\\\"",
}, },
&labeledExpr{ &labeledExpr{
pos: position{line: 230, col: 10, offset: 6593}, pos: position{line: 234, col: 10, offset: 6755},
label: "seq", label: "seq",
expr: &ruleRefExpr{ expr: &ruleRefExpr{
pos: position{line: 230, col: 14, offset: 6597}, pos: position{line: 234, col: 14, offset: 6759},
name: "EscapeSequenceCharacter", name: "EscapeSequenceCharacter",
}, },
}, },
@ -1400,85 +1427,85 @@ var g = &grammar{
}, },
{ {
name: "EscapeSequenceCharacter", name: "EscapeSequenceCharacter",
pos: position{line: 232, col: 1, offset: 6642}, pos: position{line: 236, col: 1, offset: 6804},
expr: &labeledExpr{ expr: &labeledExpr{
pos: position{line: 232, col: 28, offset: 6669}, pos: position{line: 236, col: 28, offset: 6831},
label: "char", label: "char",
expr: &ruleRefExpr{ expr: &ruleRefExpr{
pos: position{line: 232, col: 33, offset: 6674}, pos: position{line: 236, col: 33, offset: 6836},
name: "EscapeCharacter", name: "EscapeCharacter",
}, },
}, },
}, },
{ {
name: "EscapeCharacter", name: "EscapeCharacter",
pos: position{line: 234, col: 1, offset: 6691}, pos: position{line: 238, col: 1, offset: 6853},
expr: &choiceExpr{ expr: &choiceExpr{
pos: position{line: 234, col: 20, offset: 6710}, pos: position{line: 238, col: 20, offset: 6872},
alternatives: []any{ alternatives: []any{
&litMatcher{ &litMatcher{
pos: position{line: 234, col: 20, offset: 6710}, pos: position{line: 238, col: 20, offset: 6872},
val: "'", val: "'",
ignoreCase: false, ignoreCase: false,
want: "\"'\"", want: "\"'\"",
}, },
&litMatcher{ &litMatcher{
pos: position{line: 235, col: 5, offset: 6718}, pos: position{line: 239, col: 5, offset: 6880},
val: "\"", val: "\"",
ignoreCase: false, ignoreCase: false,
want: "\"\\\"\"", want: "\"\\\"\"",
}, },
&litMatcher{ &litMatcher{
pos: position{line: 236, col: 5, offset: 6726}, pos: position{line: 240, col: 5, offset: 6888},
val: "\\", val: "\\",
ignoreCase: false, ignoreCase: false,
want: "\"\\\\\"", want: "\"\\\\\"",
}, },
&actionExpr{ &actionExpr{
pos: position{line: 237, col: 5, offset: 6735}, pos: position{line: 241, col: 5, offset: 6897},
run: (*parser).callonEscapeCharacter5, run: (*parser).callonEscapeCharacter5,
expr: &litMatcher{ expr: &litMatcher{
pos: position{line: 237, col: 5, offset: 6735}, pos: position{line: 241, col: 5, offset: 6897},
val: "b", val: "b",
ignoreCase: false, ignoreCase: false,
want: "\"b\"", want: "\"b\"",
}, },
}, },
&actionExpr{ &actionExpr{
pos: position{line: 238, col: 5, offset: 6764}, pos: position{line: 242, col: 5, offset: 6926},
run: (*parser).callonEscapeCharacter7, run: (*parser).callonEscapeCharacter7,
expr: &litMatcher{ expr: &litMatcher{
pos: position{line: 238, col: 5, offset: 6764}, pos: position{line: 242, col: 5, offset: 6926},
val: "f", val: "f",
ignoreCase: false, ignoreCase: false,
want: "\"f\"", want: "\"f\"",
}, },
}, },
&actionExpr{ &actionExpr{
pos: position{line: 239, col: 5, offset: 6793}, pos: position{line: 243, col: 5, offset: 6955},
run: (*parser).callonEscapeCharacter9, run: (*parser).callonEscapeCharacter9,
expr: &litMatcher{ expr: &litMatcher{
pos: position{line: 239, col: 5, offset: 6793}, pos: position{line: 243, col: 5, offset: 6955},
val: "n", val: "n",
ignoreCase: false, ignoreCase: false,
want: "\"n\"", want: "\"n\"",
}, },
}, },
&actionExpr{ &actionExpr{
pos: position{line: 240, col: 5, offset: 6822}, pos: position{line: 244, col: 5, offset: 6984},
run: (*parser).callonEscapeCharacter11, run: (*parser).callonEscapeCharacter11,
expr: &litMatcher{ expr: &litMatcher{
pos: position{line: 240, col: 5, offset: 6822}, pos: position{line: 244, col: 5, offset: 6984},
val: "r", val: "r",
ignoreCase: false, ignoreCase: false,
want: "\"r\"", want: "\"r\"",
}, },
}, },
&actionExpr{ &actionExpr{
pos: position{line: 241, col: 5, offset: 6851}, pos: position{line: 245, col: 5, offset: 7013},
run: (*parser).callonEscapeCharacter13, run: (*parser).callonEscapeCharacter13,
expr: &litMatcher{ expr: &litMatcher{
pos: position{line: 241, col: 5, offset: 6851}, pos: position{line: 245, col: 5, offset: 7013},
val: "t", val: "t",
ignoreCase: false, ignoreCase: false,
want: "\"t\"", want: "\"t\"",
@ -1489,25 +1516,25 @@ var g = &grammar{
}, },
{ {
name: "non_escape_character", name: "non_escape_character",
pos: position{line: 243, col: 1, offset: 6877}, pos: position{line: 247, col: 1, offset: 7039},
expr: &actionExpr{ expr: &actionExpr{
pos: position{line: 243, col: 25, offset: 6901}, pos: position{line: 247, col: 25, offset: 7063},
run: (*parser).callonnon_escape_character1, run: (*parser).callonnon_escape_character1,
expr: &seqExpr{ expr: &seqExpr{
pos: position{line: 243, col: 25, offset: 6901}, pos: position{line: 247, col: 25, offset: 7063},
exprs: []any{ exprs: []any{
&notExpr{ &notExpr{
pos: position{line: 243, col: 25, offset: 6901}, pos: position{line: 247, col: 25, offset: 7063},
expr: &ruleRefExpr{ expr: &ruleRefExpr{
pos: position{line: 243, col: 27, offset: 6903}, pos: position{line: 247, col: 27, offset: 7065},
name: "escape_character", name: "escape_character",
}, },
}, },
&labeledExpr{ &labeledExpr{
pos: position{line: 243, col: 45, offset: 6921}, pos: position{line: 247, col: 45, offset: 7083},
label: "char", label: "char",
expr: &anyMatcher{ expr: &anyMatcher{
line: 243, col: 50, offset: 6926, line: 247, col: 50, offset: 7088,
}, },
}, },
}, },
@ -1516,11 +1543,11 @@ var g = &grammar{
}, },
{ {
name: "ws", name: "ws",
pos: position{line: 246, col: 1, offset: 6965}, pos: position{line: 250, col: 1, offset: 7127},
expr: &zeroOrMoreExpr{ expr: &zeroOrMoreExpr{
pos: position{line: 246, col: 7, offset: 6971}, pos: position{line: 250, col: 7, offset: 7133},
expr: &charClassMatcher{ expr: &charClassMatcher{
pos: position{line: 246, col: 7, offset: 6971}, pos: position{line: 250, col: 7, offset: 7133},
val: "[ \\t\\n\\r]", val: "[ \\t\\n\\r]",
chars: []rune{' ', '\t', '\n', '\r'}, chars: []rune{' ', '\t', '\n', '\r'},
ignoreCase: false, ignoreCase: false,
@ -1530,11 +1557,11 @@ var g = &grammar{
}, },
{ {
name: "EOF", name: "EOF",
pos: position{line: 248, col: 1, offset: 6983}, pos: position{line: 252, col: 1, offset: 7145},
expr: &notExpr{ expr: &notExpr{
pos: position{line: 248, col: 8, offset: 6990}, pos: position{line: 252, col: 8, offset: 7152},
expr: &anyMatcher{ 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() 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) { func (c *current) onIntegerLiteral1(number any) (any, error) {
return parsers.Constant{Type: parsers.ConstantTypeInteger, Value: number.(int)}, nil return parsers.Constant{Type: parsers.ConstantTypeInteger, Value: number.(int)}, nil
} }

View File

@ -205,7 +205,11 @@ ComparisonOperator <- "=" / "!=" / "<" / "<=" / ">" / ">=" {
return string(c.text), nil 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 { IntegerLiteral <- number:Integer {
return parsers.Constant{Type: parsers.ConstantTypeInteger, Value: number.(int)}, nil return parsers.Constant{Type: parsers.ConstantTypeInteger, Value: number.(int)}, nil

View File

@ -233,7 +233,8 @@ func Test_Parse(t *testing.T) {
WHERE c.boolean=true WHERE c.boolean=true
AND c.integer=1 AND c.integer=1
AND c.float=6.9 AND c.float=6.9
AND c.string="hello"`, AND c.string="hello"
AND c.param=@param_id1`,
parsers.SelectStmt{ parsers.SelectStmt{
SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}}, SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}, Alias: ""}},
Table: parsers.Table{Value: "c"}, Table: parsers.Table{Value: "c"},
@ -241,22 +242,27 @@ func Test_Parse(t *testing.T) {
Expressions: []interface{}{ Expressions: []interface{}{
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "boolean"}}, Left: parsers.SelectItem{Path: []string{"c", "boolean"}},
Right: parsers.Constant{Type: 3, Value: true}, Right: parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: true},
Operation: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "integer"}}, Left: parsers.SelectItem{Path: []string{"c", "integer"}},
Right: parsers.Constant{Type: 1, Value: 1}, Right: parsers.Constant{Type: parsers.ConstantTypeInteger, Value: 1},
Operation: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "float"}}, 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: "=", Operation: "=",
}, },
parsers.ComparisonExpression{ parsers.ComparisonExpression{
Left: parsers.SelectItem{Path: []string{"c", "string"}}, 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: "=", Operation: "=",
}, },
}, },

View File

@ -13,7 +13,7 @@ func Execute(query parsers.SelectStmt, data []RowType) []RowType {
// Iterate over each row in the data // Iterate over each row in the data
for _, row := range data { for _, row := range data {
// Check if the row satisfies the filter conditions // 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)) 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 // 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 { if expr == nil {
return true return true
} }
switch typedValue := expr.(type) { switch typedValue := expr.(type) {
case parsers.ComparisonExpression: case parsers.ComparisonExpression:
leftValue := getExpressionParameterValue(typedValue.Left, row) leftValue := getExpressionParameterValue(typedValue.Left, Parameters, row)
rightValue := getExpressionParameterValue(typedValue.Right, row) rightValue := getExpressionParameterValue(typedValue.Right, Parameters, row)
switch typedValue.Operation { switch typedValue.Operation {
case "=": case "=":
@ -71,7 +71,7 @@ func evaluateFilters(expr ExpressionType, row RowType) bool {
case parsers.LogicalExpression: case parsers.LogicalExpression:
var result bool var result bool
for i, expression := range typedValue.Expressions { for i, expression := range typedValue.Expressions {
expressionResult := evaluateFilters(expression, row) expressionResult := evaluateFilters(expression, Parameters, row)
if i == 0 { if i == 0 {
result = expressionResult result = expressionResult
} }
@ -124,11 +124,22 @@ func getFieldValue(field parsers.SelectItem, row RowType) interface{} {
return value return value
} }
func getExpressionParameterValue(parameter interface{}, row RowType) interface{} { func getExpressionParameterValue(
parameter interface{},
Parameters map[string]interface{},
row RowType,
) interface{} {
switch typedParameter := parameter.(type) { switch typedParameter := parameter.(type) {
case parsers.SelectItem: case parsers.SelectItem:
return getFieldValue(typedParameter, row) return getFieldValue(typedParameter, row)
case parsers.Constant: case parsers.Constant:
if typedParameter.Type == parsers.ConstantTypeParameterConstant &&
Parameters != nil {
if key, ok := typedParameter.Value.(string); ok {
return Parameters[key]
}
}
return typedParameter.Value return typedParameter.Value
} }
// TODO: Handle error // TODO: Handle error

View File

@ -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) { t.Run("Should execute SELECT with multiple WHERE conditions", func(t *testing.T) {
testQueryExecute( testQueryExecute(
t, t,