mirror of
https://github.com/pikami/cosmium.git
synced 2026-07-21 13:08:49 +01:00
Support single-quoted string literals in query parsing (#18)
* Support single-quoted string literals in query parsing
Add single-quoted string literal support to the NoSQL query parser,
matching Cosmos DB behavior where apostrophes are escaped by doubling
them (''). Includes parser unit tests and an API integration test.
Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>
* Use backslash escape for apostrophes in single-quoted strings
Cosmos DB escapes apostrophes with \' rather than SQL-style doubling.
Also fix EscapeCharacter to return proper values for \', \", and \\
which were previously producing empty substitutions.
Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
@@ -134,6 +134,34 @@ func Test_Documents(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should query document with single-quoted string literal", func(t *testing.T) {
|
||||
testCosmosQuery(t, collectionClient,
|
||||
`select c.id
|
||||
FROM c
|
||||
WHERE c.id='67890'
|
||||
ORDER BY c.id`,
|
||||
nil,
|
||||
[]interface{}{
|
||||
map[string]interface{}{"id": "67890"},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should query document with escaped single-quoted string literal", func(t *testing.T) {
|
||||
ts.DataStore.CreateDocument(testDatabaseName, testCollectionName, map[string]interface{}{"id": "apostrophe-doc", "pk": "ap", "name": "it's"})
|
||||
|
||||
testCosmosQuery(t, collectionClient,
|
||||
`select c.id
|
||||
FROM c
|
||||
WHERE c.name='it\'s'
|
||||
ORDER BY c.id`,
|
||||
nil,
|
||||
[]interface{}{
|
||||
map[string]interface{}{"id": "apostrophe-doc"},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should query document with query parameters", func(t *testing.T) {
|
||||
testCosmosQuery(t, collectionClient,
|
||||
`select c.id
|
||||
|
||||
+1508
-1335
File diff suppressed because it is too large
Load Diff
@@ -509,6 +509,8 @@ IntegerLiteral <- number:Integer {
|
||||
}
|
||||
StringLiteral <- "\"" chars:StringCharacter* "\"" {
|
||||
return parsers.Constant{Type: parsers.ConstantTypeString,Value: joinStrings(chars.([]interface{}))}, nil
|
||||
} / "'" chars:SingleQuotedStringCharacter* "'" {
|
||||
return parsers.Constant{Type: parsers.ConstantTypeString,Value: joinStrings(chars.([]interface{}))}, nil
|
||||
}
|
||||
FloatLiteral <- [0-9]+"."[0-9]+ {
|
||||
floatValue, _ := strconv.ParseFloat(string(c.text), 64)
|
||||
@@ -869,11 +871,14 @@ Integer <- [0-9]+ {
|
||||
StringCharacter <- !('"' / "\\") . { return string(c.text), nil }
|
||||
/ "\\" seq:EscapeSequenceCharacter { return seq, nil }
|
||||
|
||||
SingleQuotedStringCharacter <- !("'" / "\\") . { return string(c.text), nil }
|
||||
/ "\\" seq:EscapeSequenceCharacter { return seq, nil }
|
||||
|
||||
EscapeSequenceCharacter <- char:EscapeCharacter
|
||||
|
||||
EscapeCharacter <- "'"
|
||||
/ '"'
|
||||
/ "\\"
|
||||
EscapeCharacter <- "'" { return "'", nil }
|
||||
/ '"' { return "\"", nil }
|
||||
/ "\\" { return "\\", nil }
|
||||
/ "b" { return "\b", nil }
|
||||
/ "f" { return "\f", nil }
|
||||
/ "n" { return "\n", nil }
|
||||
|
||||
@@ -149,6 +149,32 @@ func Test_Parse_Were(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should correctly parse single-quoted string literals", func(t *testing.T) {
|
||||
testQueryParse(
|
||||
t,
|
||||
`SELECT c.id FROM c WHERE c.id = '12345' AND c.string = 'it\'s'`,
|
||||
parsers.SelectStmt{
|
||||
SelectItems: []parsers.SelectItem{{Path: []string{"c", "id"}}},
|
||||
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||
Filters: parsers.LogicalExpression{
|
||||
Operation: parsers.LogicalExpressionTypeAnd,
|
||||
Expressions: []interface{}{
|
||||
parsers.ComparisonExpression{
|
||||
Operation: "=",
|
||||
Left: parsers.SelectItem{Path: []string{"c", "id"}},
|
||||
Right: testutils.SelectItem_Constant_String("12345"),
|
||||
},
|
||||
parsers.ComparisonExpression{
|
||||
Operation: "=",
|
||||
Left: parsers.SelectItem{Path: []string{"c", "string"}},
|
||||
Right: testutils.SelectItem_Constant_String("it's"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should correctly parse NOT conditions", func(t *testing.T) {
|
||||
testQueryParse(
|
||||
t,
|
||||
|
||||
Reference in New Issue
Block a user