Add support for subqueries

This commit is contained in:
Pijus Kamandulis
2024-12-07 22:29:26 +02:00
parent 3584f9b5ce
commit 66ea859f34
15 changed files with 3227 additions and 2290 deletions

View File

@@ -122,4 +122,25 @@ func Test_Parse(t *testing.T) {
},
)
})
t.Run("Should parse IN selector", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id FROM c IN c.tags`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
)
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,16 +4,19 @@ package nosql
import "github.com/pikami/cosmium/parsers"
func makeSelectStmt(
columns, table, joinItems,
columns, fromClause, joinItems,
whereClause interface{}, distinctClause interface{},
count interface{}, groupByClause interface{}, orderList interface{},
offsetClause interface{},
) (parsers.SelectStmt, error) {
selectStmt := parsers.SelectStmt{
SelectItems: columns.([]parsers.SelectItem),
Table: table.(parsers.Table),
}
if fromTable, ok := fromClause.(parsers.Table); ok {
selectStmt.Table = fromTable
}
if joinItemsArray, ok := joinItems.([]interface{}); ok && len(joinItemsArray) > 0 {
selectStmt.JoinItems = make([]parsers.JoinItem, len(joinItemsArray))
for i, joinItem := range joinItemsArray {
@@ -56,10 +59,18 @@ func makeSelectStmt(
}
func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) {
return parsers.JoinItem{
Table: table.(parsers.Table),
SelectItem: column.(parsers.SelectItem),
}, nil
joinItem := parsers.JoinItem{}
if selectItem, isSelectItem := column.(parsers.SelectItem); isSelectItem {
joinItem.SelectItem = selectItem
joinItem.Table.Value = selectItem.Alias
}
if tableTyped, isTable := table.(parsers.Table); isTable {
joinItem.Table = tableTyped
}
return joinItem, nil
}
func makeSelectItem(name interface{}, path interface{}, selectItemType parsers.SelectItemType) (parsers.SelectItem, error) {
@@ -177,13 +188,13 @@ SelectStmt <- Select ws
distinctClause:DistinctClause? ws
topClause:TopClause? ws
columns:Selection ws
From ws table:TableName ws
joinClauses:JoinClause* ws
fromClause:FromClause? ws
joinClauses:(ws join:JoinClause { return join, nil })* ws
whereClause:(ws Where ws condition:Condition { return condition, nil })?
groupByClause:(ws GroupBy ws columns:ColumnList { return columns, nil })?
orderByClause:OrderByClause?
offsetClause:OffsetClause? {
return makeSelectStmt(columns, table, joinClauses, whereClause,
orderByClause:(ws order:OrderByClause { return order, nil })?
offsetClause:(ws offset:OffsetClause { return offset, nil })? {
return makeSelectStmt(columns, fromClause, joinClauses, whereClause,
distinctClause, topClause, groupByClause, orderByClause, offsetClause)
}
@@ -193,8 +204,49 @@ TopClause <- Top ws count:Integer {
return count, nil
}
FromClause <- From ws table:TableName selectItem:(ws "IN"i ws column:SelectItem { return column, nil })? {
tableTyped := table.(parsers.Table)
if selectItem != nil {
tableTyped.SelectItem = selectItem.(parsers.SelectItem)
}
return tableTyped, nil
} / From ws subQuery:SubQuerySelectItem {
subQueryTyped := subQuery.(parsers.SelectItem)
table := parsers.Table{
Value: subQueryTyped.Alias,
SelectItem: subQueryTyped,
}
return table, nil
}
SubQuery <- exists:(exists:Exists ws { return exists, nil })? "(" ws selectStmt:SelectStmt ws ")" {
if selectStatement, isGoodValue := selectStmt.(parsers.SelectStmt); isGoodValue {
selectStatement.Exists = exists != nil
return selectStatement, nil
}
return selectStmt, nil
}
SubQuerySelectItem <- subQuery:SubQuery asClause:(ws alias:AsClause { return alias, nil })? {
selectItem := parsers.SelectItem{
Type: parsers.SelectItemTypeSubQuery,
Value: subQuery,
}
if tableName, isString := asClause.(string); isString {
selectItem.Alias = tableName
}
return selectItem, nil
}
JoinClause <- Join ws table:TableName ws "IN"i ws column:SelectItem {
return makeJoin(table, column)
} / Join ws subQuery:SubQuerySelectItem {
return makeJoin(nil, subQuery)
}
OffsetClause <- "OFFSET"i ws offset:IntegerLiteral ws "LIMIT"i ws limit:IntegerLiteral {
@@ -241,7 +293,7 @@ SelectProperty <- name:Identifier path:(DotFieldAccess / ArrayFieldAccess)* {
return makeSelectItem(name, path, parsers.SelectItemTypeField)
}
SelectItem <- selectItem:(Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
SelectItem <- selectItem:(SubQuerySelectItem / Literal / FunctionCall / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
var itemResult parsers.SelectItem
switch typedValue := selectItem.(type) {
case parsers.SelectItem:
@@ -322,11 +374,13 @@ From <- "FROM"i
Join <- "JOIN"i
Exists <- "EXISTS"i
Where <- "WHERE"i
And <- "AND"i
Or <- "OR"i
Or <- "OR"i wss
GroupBy <- "GROUP"i ws "BY"i
@@ -677,4 +731,6 @@ non_escape_character <- !(escape_character) char:.
ws <- [ \t\n\r]*
wss <- [ \t\n\r]+
EOF <- !.

View File

@@ -0,0 +1,125 @@
package nosql_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
)
func Test_Parse_SubQuery(t *testing.T) {
t.Run("Should parse FROM subquery", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id FROM (SELECT VALUE cc["info"] FROM cc) AS c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Alias: "c",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
Table: parsers.Table{Value: "cc"},
SelectItems: []parsers.SelectItem{
{Path: []string{"cc", "info"}, IsTopLevel: true},
},
},
},
},
},
)
})
t.Run("Should parse JOIN subquery", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id, cc.name FROM c JOIN (SELECT tag.name FROM tag IN c.tags) AS cc`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{Path: []string{"cc", "name"}},
},
Table: parsers.Table{
Value: "c",
},
JoinItems: []parsers.JoinItem{
{
Table: parsers.Table{
Value: "cc",
},
SelectItem: parsers.SelectItem{
Alias: "cc",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}},
},
Table: parsers.Table{
Value: "tag",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
},
},
},
},
)
})
t.Run("Should parse JOIN EXISTS subquery", func(t *testing.T) {
testQueryParse(
t,
`SELECT c.id
FROM c
JOIN (
SELECT VALUE EXISTS(SELECT tag.name FROM tag IN c.tags)
) AS hasTags
WHERE hasTags`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
},
Table: parsers.Table{
Value: "c",
},
JoinItems: []parsers.JoinItem{
{
Table: parsers.Table{Value: "hasTags"},
SelectItem: parsers.SelectItem{
Alias: "hasTags",
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
IsTopLevel: true,
Type: parsers.SelectItemTypeSubQuery,
Value: parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"tag", "name"}},
},
Table: parsers.Table{
Value: "tag",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
Exists: true,
},
},
},
},
},
},
},
Filters: parsers.SelectItem{
Path: []string{"hasTags"},
},
},
)
})
}