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

@ -111,7 +111,7 @@ func ExecuteQueryDocuments(databaseId string, collectionId string, query string,
if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok { if typedQuery, ok := parsedQuery.(parsers.SelectStmt); ok {
typedQuery.Parameters = queryParameters typedQuery.Parameters = queryParameters
return memoryexecutor.Execute(typedQuery, covDocs), repositorymodels.StatusOk return memoryexecutor.ExecuteQuery(typedQuery, covDocs), repositorymodels.StatusOk
} }
return nil, repositorymodels.BadRequest return nil, repositorymodels.BadRequest

View File

@ -5,6 +5,7 @@ type SelectStmt struct {
Table Table Table Table
JoinItems []JoinItem JoinItems []JoinItem
Filters interface{} Filters interface{}
Exists bool
Distinct bool Distinct bool
Count int Count int
Offset int Offset int
@ -15,6 +16,7 @@ type SelectStmt struct {
type Table struct { type Table struct {
Value string Value string
SelectItem SelectItem
} }
type JoinItem struct { type JoinItem struct {
@ -30,6 +32,7 @@ const (
SelectItemTypeArray SelectItemTypeArray
SelectItemTypeConstant SelectItemTypeConstant
SelectItemTypeFunctionCall SelectItemTypeFunctionCall
SelectItemTypeSubQuery
) )
type SelectItem struct { type SelectItem struct {

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,14 +4,17 @@ package nosql
import "github.com/pikami/cosmium/parsers" import "github.com/pikami/cosmium/parsers"
func makeSelectStmt( func makeSelectStmt(
columns, table, joinItems, columns, fromClause, joinItems,
whereClause interface{}, distinctClause interface{}, whereClause interface{}, distinctClause interface{},
count interface{}, groupByClause interface{}, orderList interface{}, count interface{}, groupByClause interface{}, orderList interface{},
offsetClause interface{}, offsetClause interface{},
) (parsers.SelectStmt, error) { ) (parsers.SelectStmt, error) {
selectStmt := parsers.SelectStmt{ selectStmt := parsers.SelectStmt{
SelectItems: columns.([]parsers.SelectItem), 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 { if joinItemsArray, ok := joinItems.([]interface{}); ok && len(joinItemsArray) > 0 {
@ -56,10 +59,18 @@ func makeSelectStmt(
} }
func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) { func makeJoin(table interface{}, column interface{}) (parsers.JoinItem, error) {
return parsers.JoinItem{ joinItem := parsers.JoinItem{}
Table: table.(parsers.Table),
SelectItem: column.(parsers.SelectItem), if selectItem, isSelectItem := column.(parsers.SelectItem); isSelectItem {
}, nil 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) { func makeSelectItem(name interface{}, path interface{}, selectItemType parsers.SelectItemType) (parsers.SelectItem, error) {
@ -177,13 +188,13 @@ SelectStmt <- Select ws
distinctClause:DistinctClause? ws distinctClause:DistinctClause? ws
topClause:TopClause? ws topClause:TopClause? ws
columns:Selection ws columns:Selection ws
From ws table:TableName ws fromClause:FromClause? ws
joinClauses:JoinClause* ws joinClauses:(ws join:JoinClause { return join, nil })* ws
whereClause:(ws Where ws condition:Condition { return condition, nil })? whereClause:(ws Where ws condition:Condition { return condition, nil })?
groupByClause:(ws GroupBy ws columns:ColumnList { return columns, nil })? groupByClause:(ws GroupBy ws columns:ColumnList { return columns, nil })?
orderByClause:OrderByClause? orderByClause:(ws order:OrderByClause { return order, nil })?
offsetClause:OffsetClause? { offsetClause:(ws offset:OffsetClause { return offset, nil })? {
return makeSelectStmt(columns, table, joinClauses, whereClause, return makeSelectStmt(columns, fromClause, joinClauses, whereClause,
distinctClause, topClause, groupByClause, orderByClause, offsetClause) distinctClause, topClause, groupByClause, orderByClause, offsetClause)
} }
@ -193,8 +204,49 @@ TopClause <- Top ws count:Integer {
return count, nil 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 { JoinClause <- Join ws table:TableName ws "IN"i ws column:SelectItem {
return makeJoin(table, column) return makeJoin(table, column)
} / Join ws subQuery:SubQuerySelectItem {
return makeJoin(nil, subQuery)
} }
OffsetClause <- "OFFSET"i ws offset:IntegerLiteral ws "LIMIT"i ws limit:IntegerLiteral { 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) 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 var itemResult parsers.SelectItem
switch typedValue := selectItem.(type) { switch typedValue := selectItem.(type) {
case parsers.SelectItem: case parsers.SelectItem:
@ -322,11 +374,13 @@ From <- "FROM"i
Join <- "JOIN"i Join <- "JOIN"i
Exists <- "EXISTS"i
Where <- "WHERE"i Where <- "WHERE"i
And <- "AND"i And <- "AND"i
Or <- "OR"i Or <- "OR"i wss
GroupBy <- "GROUP"i ws "BY"i GroupBy <- "GROUP"i ws "BY"i
@ -677,4 +731,6 @@ non_escape_character <- !(escape_character) char:.
ws <- [ \t\n\r]* ws <- [ \t\n\r]*
wss <- [ \t\n\r]+
EOF <- !. 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"},
},
},
)
})
}

View File

@ -6,14 +6,13 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Avg(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
sum := 0.0 sum := 0.0
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
sum += numericValue sum += numericValue
count++ count++
@ -22,7 +21,6 @@ func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return sum / float64(count) return sum / float64(count)
@ -31,30 +29,27 @@ func (c memoryExecutorContext) aggregate_Avg(arguments []interface{}, row RowTyp
} }
} }
func (c memoryExecutorContext) aggregate_Count(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Count(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if value != nil { if value != nil {
count++ count++
} }
} }
}
return count return count
} }
func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Max(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
max := 0.0 max := 0.0
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
if numericValue > max { if numericValue > max {
max = numericValue max = numericValue
@ -67,7 +62,6 @@ func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return max return max
@ -76,14 +70,13 @@ func (c memoryExecutorContext) aggregate_Max(arguments []interface{}, row RowTyp
} }
} }
func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Min(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
min := math.MaxFloat64 min := math.MaxFloat64
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
if numericValue < min { if numericValue < min {
min = numericValue min = numericValue
@ -96,7 +89,6 @@ func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return min return min
@ -105,14 +97,13 @@ func (c memoryExecutorContext) aggregate_Min(arguments []interface{}, row RowTyp
} }
} }
func (c memoryExecutorContext) aggregate_Sum(arguments []interface{}, row RowType) interface{} { func (r rowContext) aggregate_Sum(arguments []interface{}) interface{} {
selectExpression := arguments[0].(parsers.SelectItem) selectExpression := arguments[0].(parsers.SelectItem)
sum := 0.0 sum := 0.0
count := 0 count := 0
if array, isArray := row.([]RowWithJoins); isArray { for _, item := range r.grouppedRows {
for _, item := range array { value := item.resolveSelectItem(selectExpression)
value := c.getFieldValue(selectExpression, item)
if numericValue, ok := value.(float64); ok { if numericValue, ok := value.(float64); ok {
sum += numericValue sum += numericValue
count++ count++
@ -121,7 +112,6 @@ func (c memoryExecutorContext) aggregate_Sum(arguments []interface{}, row RowTyp
count++ count++
} }
} }
}
if count > 0 { if count > 0 {
return sum return sum

View File

@ -7,17 +7,17 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) array_Concat(arguments []interface{}, row RowType) []interface{} { func (r rowContext) array_Concat(arguments []interface{}) []interface{} {
var result []interface{} var result []interface{}
for _, arg := range arguments { for _, arg := range arguments {
array := c.parseArray(arg, row) array := r.parseArray(arg)
result = append(result, array...) result = append(result, array...)
} }
return result return result
} }
func (c memoryExecutorContext) array_Length(arguments []interface{}, row RowType) int { func (r rowContext) array_Length(arguments []interface{}) int {
array := c.parseArray(arguments[0], row) array := r.parseArray(arguments[0])
if array == nil { if array == nil {
return 0 return 0
} }
@ -25,15 +25,15 @@ func (c memoryExecutorContext) array_Length(arguments []interface{}, row RowType
return len(array) return len(array)
} }
func (c memoryExecutorContext) array_Slice(arguments []interface{}, row RowType) []interface{} { func (r rowContext) array_Slice(arguments []interface{}) []interface{} {
var ok bool var ok bool
var start int var start int
var length int var length int
array := c.parseArray(arguments[0], row) array := r.parseArray(arguments[0])
startEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) startEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if arguments[2] != nil { if arguments[2] != nil {
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("array_Slice - got length parameters of wrong type") logger.Error("array_Slice - got length parameters of wrong type")
@ -65,9 +65,9 @@ func (c memoryExecutorContext) array_Slice(arguments []interface{}, row RowType)
return array[start:end] return array[start:end]
} }
func (c memoryExecutorContext) set_Intersect(arguments []interface{}, row RowType) []interface{} { func (r rowContext) set_Intersect(arguments []interface{}) []interface{} {
set1 := c.parseArray(arguments[0], row) set1 := r.parseArray(arguments[0])
set2 := c.parseArray(arguments[1], row) set2 := r.parseArray(arguments[1])
intersection := make(map[interface{}]struct{}) intersection := make(map[interface{}]struct{})
if set1 == nil || set2 == nil { if set1 == nil || set2 == nil {
@ -88,9 +88,9 @@ func (c memoryExecutorContext) set_Intersect(arguments []interface{}, row RowTyp
return result return result
} }
func (c memoryExecutorContext) set_Union(arguments []interface{}, row RowType) []interface{} { func (r rowContext) set_Union(arguments []interface{}) []interface{} {
set1 := c.parseArray(arguments[0], row) set1 := r.parseArray(arguments[0])
set2 := c.parseArray(arguments[1], row) set2 := r.parseArray(arguments[1])
var result []interface{} var result []interface{}
union := make(map[interface{}]struct{}) union := make(map[interface{}]struct{})
@ -111,9 +111,9 @@ func (c memoryExecutorContext) set_Union(arguments []interface{}, row RowType) [
return result return result
} }
func (c memoryExecutorContext) parseArray(argument interface{}, row RowType) []interface{} { func (r rowContext) parseArray(argument interface{}) []interface{} {
exItem := argument.(parsers.SelectItem) exItem := argument.(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
arrValue := reflect.ValueOf(ex) arrValue := reflect.ValueOf(ex)
if arrValue.Kind() != reflect.Slice { if arrValue.Kind() != reflect.Slice {

View File

@ -8,9 +8,9 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) math_Abs(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Abs(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@ -26,9 +26,9 @@ func (c memoryExecutorContext) math_Abs(arguments []interface{}, row RowType) in
} }
} }
func (c memoryExecutorContext) math_Acos(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Acos(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -44,9 +44,9 @@ func (c memoryExecutorContext) math_Acos(arguments []interface{}, row RowType) i
return math.Acos(val) * 180 / math.Pi return math.Acos(val) * 180 / math.Pi
} }
func (c memoryExecutorContext) math_Asin(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Asin(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -62,9 +62,9 @@ func (c memoryExecutorContext) math_Asin(arguments []interface{}, row RowType) i
return math.Asin(val) * 180 / math.Pi return math.Asin(val) * 180 / math.Pi
} }
func (c memoryExecutorContext) math_Atan(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Atan(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -75,9 +75,9 @@ func (c memoryExecutorContext) math_Atan(arguments []interface{}, row RowType) i
return math.Atan(val) * 180 / math.Pi return math.Atan(val) * 180 / math.Pi
} }
func (c memoryExecutorContext) math_Ceiling(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Ceiling(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@ -90,9 +90,9 @@ func (c memoryExecutorContext) math_Ceiling(arguments []interface{}, row RowType
} }
} }
func (c memoryExecutorContext) math_Cos(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Cos(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -103,9 +103,9 @@ func (c memoryExecutorContext) math_Cos(arguments []interface{}, row RowType) in
return math.Cos(val) return math.Cos(val)
} }
func (c memoryExecutorContext) math_Cot(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Cot(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -121,9 +121,9 @@ func (c memoryExecutorContext) math_Cot(arguments []interface{}, row RowType) in
return 1 / math.Tan(val) return 1 / math.Tan(val)
} }
func (c memoryExecutorContext) math_Degrees(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Degrees(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -134,9 +134,9 @@ func (c memoryExecutorContext) math_Degrees(arguments []interface{}, row RowType
return val * (180 / math.Pi) return val * (180 / math.Pi)
} }
func (c memoryExecutorContext) math_Exp(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Exp(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -147,9 +147,9 @@ func (c memoryExecutorContext) math_Exp(arguments []interface{}, row RowType) in
return math.Exp(val) return math.Exp(val)
} }
func (c memoryExecutorContext) math_Floor(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Floor(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@ -162,9 +162,9 @@ func (c memoryExecutorContext) math_Floor(arguments []interface{}, row RowType)
} }
} }
func (c memoryExecutorContext) math_IntBitNot(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitNot(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case int: case int:
@ -175,9 +175,9 @@ func (c memoryExecutorContext) math_IntBitNot(arguments []interface{}, row RowTy
} }
} }
func (c memoryExecutorContext) math_Log10(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Log10(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -193,9 +193,9 @@ func (c memoryExecutorContext) math_Log10(arguments []interface{}, row RowType)
return math.Log10(val) return math.Log10(val)
} }
func (c memoryExecutorContext) math_Radians(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Radians(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -206,9 +206,9 @@ func (c memoryExecutorContext) math_Radians(arguments []interface{}, row RowType
return val * (math.Pi / 180.0) return val * (math.Pi / 180.0)
} }
func (c memoryExecutorContext) math_Round(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Round(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@ -221,9 +221,9 @@ func (c memoryExecutorContext) math_Round(arguments []interface{}, row RowType)
} }
} }
func (c memoryExecutorContext) math_Sign(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Sign(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@ -248,9 +248,9 @@ func (c memoryExecutorContext) math_Sign(arguments []interface{}, row RowType) i
} }
} }
func (c memoryExecutorContext) math_Sin(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Sin(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -261,9 +261,9 @@ func (c memoryExecutorContext) math_Sin(arguments []interface{}, row RowType) in
return math.Sin(val) return math.Sin(val)
} }
func (c memoryExecutorContext) math_Sqrt(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Sqrt(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -274,9 +274,9 @@ func (c memoryExecutorContext) math_Sqrt(arguments []interface{}, row RowType) i
return math.Sqrt(val) return math.Sqrt(val)
} }
func (c memoryExecutorContext) math_Square(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Square(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -287,9 +287,9 @@ func (c memoryExecutorContext) math_Square(arguments []interface{}, row RowType)
return math.Pow(val, 2) return math.Pow(val, 2)
} }
func (c memoryExecutorContext) math_Tan(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Tan(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
val, valIsNumber := numToFloat64(ex) val, valIsNumber := numToFloat64(ex)
if !valIsNumber { if !valIsNumber {
@ -300,9 +300,9 @@ func (c memoryExecutorContext) math_Tan(arguments []interface{}, row RowType) in
return math.Tan(val) return math.Tan(val)
} }
func (c memoryExecutorContext) math_Trunc(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Trunc(arguments []interface{}) interface{} {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch val := ex.(type) { switch val := ex.(type) {
case float64: case float64:
@ -315,11 +315,11 @@ func (c memoryExecutorContext) math_Trunc(arguments []interface{}, row RowType)
} }
} }
func (c memoryExecutorContext) math_Atn2(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Atn2(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
y, yIsNumber := numToFloat64(ex1) y, yIsNumber := numToFloat64(ex1)
x, xIsNumber := numToFloat64(ex2) x, xIsNumber := numToFloat64(ex2)
@ -332,11 +332,11 @@ func (c memoryExecutorContext) math_Atn2(arguments []interface{}, row RowType) i
return math.Atan2(y, x) return math.Atan2(y, x)
} }
func (c memoryExecutorContext) math_IntAdd(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntAdd(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
ex1Number, ex1IsNumber := numToInt(ex1) ex1Number, ex1IsNumber := numToInt(ex1)
ex2Number, ex2IsNumber := numToInt(ex2) ex2Number, ex2IsNumber := numToInt(ex2)
@ -349,11 +349,11 @@ func (c memoryExecutorContext) math_IntAdd(arguments []interface{}, row RowType)
return ex1Number + ex2Number return ex1Number + ex2Number
} }
func (c memoryExecutorContext) math_IntBitAnd(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitAnd(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
ex1Int, ex1IsInt := numToInt(ex1) ex1Int, ex1IsInt := numToInt(ex1)
ex2Int, ex2IsInt := numToInt(ex2) ex2Int, ex2IsInt := numToInt(ex2)
@ -366,11 +366,11 @@ func (c memoryExecutorContext) math_IntBitAnd(arguments []interface{}, row RowTy
return ex1Int & ex2Int return ex1Int & ex2Int
} }
func (c memoryExecutorContext) math_IntBitLeftShift(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitLeftShift(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := numToInt(ex1) num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2) num2, num2IsInt := numToInt(ex2)
@ -383,11 +383,11 @@ func (c memoryExecutorContext) math_IntBitLeftShift(arguments []interface{}, row
return num1 << uint(num2) return num1 << uint(num2)
} }
func (c memoryExecutorContext) math_IntBitOr(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitOr(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@ -400,11 +400,11 @@ func (c memoryExecutorContext) math_IntBitOr(arguments []interface{}, row RowTyp
return num1 | num2 return num1 | num2
} }
func (c memoryExecutorContext) math_IntBitRightShift(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitRightShift(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := numToInt(ex1) num1, num1IsInt := numToInt(ex1)
num2, num2IsInt := numToInt(ex2) num2, num2IsInt := numToInt(ex2)
@ -417,11 +417,11 @@ func (c memoryExecutorContext) math_IntBitRightShift(arguments []interface{}, ro
return num1 >> uint(num2) return num1 >> uint(num2)
} }
func (c memoryExecutorContext) math_IntBitXor(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntBitXor(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@ -434,11 +434,11 @@ func (c memoryExecutorContext) math_IntBitXor(arguments []interface{}, row RowTy
return num1 ^ num2 return num1 ^ num2
} }
func (c memoryExecutorContext) math_IntDiv(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntDiv(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@ -451,11 +451,11 @@ func (c memoryExecutorContext) math_IntDiv(arguments []interface{}, row RowType)
return num1 / num2 return num1 / num2
} }
func (c memoryExecutorContext) math_IntMul(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntMul(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@ -468,11 +468,11 @@ func (c memoryExecutorContext) math_IntMul(arguments []interface{}, row RowType)
return num1 * num2 return num1 * num2
} }
func (c memoryExecutorContext) math_IntSub(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntSub(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@ -485,11 +485,11 @@ func (c memoryExecutorContext) math_IntSub(arguments []interface{}, row RowType)
return num1 - num2 return num1 - num2
} }
func (c memoryExecutorContext) math_IntMod(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_IntMod(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
num1, num1IsInt := ex1.(int) num1, num1IsInt := ex1.(int)
num2, num2IsInt := ex2.(int) num2, num2IsInt := ex2.(int)
@ -502,11 +502,11 @@ func (c memoryExecutorContext) math_IntMod(arguments []interface{}, row RowType)
return num1 % num2 return num1 % num2
} }
func (c memoryExecutorContext) math_Power(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Power(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
ex1 := c.getFieldValue(exItem1, row) ex1 := r.resolveSelectItem(exItem1)
ex2 := c.getFieldValue(exItem2, row) ex2 := r.resolveSelectItem(exItem2)
base, baseIsNumber := numToFloat64(ex1) base, baseIsNumber := numToFloat64(ex1)
exponent, exponentIsNumber := numToFloat64(ex2) exponent, exponentIsNumber := numToFloat64(ex2)
@ -519,14 +519,14 @@ func (c memoryExecutorContext) math_Power(arguments []interface{}, row RowType)
return math.Pow(base, exponent) return math.Pow(base, exponent)
} }
func (c memoryExecutorContext) math_Log(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_Log(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem1, row) ex := r.resolveSelectItem(exItem1)
var base float64 = math.E var base float64 = math.E
if len(arguments) > 1 { if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
baseValueObject := c.getFieldValue(exItem2, row) baseValueObject := r.resolveSelectItem(exItem2)
baseValue, baseValueIsNumber := numToFloat64(baseValueObject) baseValue, baseValueIsNumber := numToFloat64(baseValueObject)
if !baseValueIsNumber { if !baseValueIsNumber {
@ -551,15 +551,15 @@ func (c memoryExecutorContext) math_Log(arguments []interface{}, row RowType) in
return math.Log(num) / math.Log(base) return math.Log(num) / math.Log(base)
} }
func (c memoryExecutorContext) math_NumberBin(arguments []interface{}, row RowType) interface{} { func (r rowContext) math_NumberBin(arguments []interface{}) interface{} {
exItem1 := arguments[0].(parsers.SelectItem) exItem1 := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem1, row) ex := r.resolveSelectItem(exItem1)
binSize := 1.0 binSize := 1.0
if len(arguments) > 1 { if len(arguments) > 1 {
exItem2 := arguments[1].(parsers.SelectItem) exItem2 := arguments[1].(parsers.SelectItem)
binSizeValueObject := c.getFieldValue(exItem2, row) binSizeValueObject := r.resolveSelectItem(exItem2)
binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject) binSizeValue, binSizeValueIsNumber := numToFloat64(binSizeValueObject)
if !binSizeValueIsNumber { if !binSizeValueIsNumber {
@ -584,11 +584,11 @@ func (c memoryExecutorContext) math_NumberBin(arguments []interface{}, row RowTy
return math.Floor(num/binSize) * binSize return math.Floor(num/binSize) * binSize
} }
func (c memoryExecutorContext) math_Pi() interface{} { func (r rowContext) math_Pi() interface{} {
return math.Pi return math.Pi
} }
func (c memoryExecutorContext) math_Rand() interface{} { func (r rowContext) math_Rand() interface{} {
return rand.Float64() return rand.Float64()
} }

View File

@ -13,137 +13,174 @@ import (
) )
type RowType interface{} type RowType interface{}
type RowWithJoins map[string]RowType type rowContext struct {
type ExpressionType interface{} tables map[string]RowType
type memoryExecutorContext struct {
parameters map[string]interface{} parameters map[string]interface{}
grouppedRows []rowContext
} }
func Execute(query parsers.SelectStmt, data []RowType) []RowType { func ExecuteQuery(query parsers.SelectStmt, documents []RowType) []RowType {
ctx := memoryExecutorContext{ currentDocuments := make([]rowContext, 0)
parameters: query.Parameters, for _, doc := range documents {
currentDocuments = append(currentDocuments, resolveFrom(query, doc)...)
} }
joinedRows := make([]RowWithJoins, 0) // Handle JOINS
for _, row := range data { nextDocuments := make([]rowContext, 0)
// Perform joins for _, currentDocument := range currentDocuments {
dataTables := map[string][]RowType{} rowContexts := currentDocument.handleJoin(query)
nextDocuments = append(nextDocuments, rowContexts...)
for _, join := range query.JoinItems {
joinedData := ctx.getFieldValue(join.SelectItem, row)
if joinedDataArray, isArray := joinedData.([]map[string]interface{}); isArray {
var rows []RowType
for _, m := range joinedDataArray {
rows = append(rows, RowType(m))
}
dataTables[join.Table.Value] = rows
}
}
// Generate flat rows
flatRows := []RowWithJoins{
{query.Table.Value: row},
}
for joinedTableName, joinedTable := range dataTables {
flatRows = zipRows(flatRows, joinedTableName, joinedTable)
} }
currentDocuments = nextDocuments
// Apply filters // Apply filters
filteredRows := []RowWithJoins{} nextDocuments = make([]rowContext, 0)
for _, rowWithJoins := range flatRows { for _, currentDocument := range currentDocuments {
if ctx.evaluateFilters(query.Filters, rowWithJoins) { if currentDocument.applyFilters(query.Filters) {
filteredRows = append(filteredRows, rowWithJoins) nextDocuments = append(nextDocuments, currentDocument)
} }
} }
currentDocuments = nextDocuments
joinedRows = append(joinedRows, filteredRows...)
}
// Apply order // Apply order
if query.OrderExpressions != nil && len(query.OrderExpressions) > 0 { if len(query.OrderExpressions) > 0 {
ctx.orderBy(query.OrderExpressions, joinedRows) applyOrder(currentDocuments, query.OrderExpressions)
} }
result := make([]RowType, 0) // Apply group by
if len(query.GroupBy) > 0 {
// Apply group currentDocuments = applyGroupBy(currentDocuments, query.GroupBy)
isGroupSelect := query.GroupBy != nil && len(query.GroupBy) > 0
if isGroupSelect {
result = ctx.groupBy(query, joinedRows)
} }
// Apply select // Apply select
if !isGroupSelect { projectedDocuments := applyProjection(currentDocuments, query.SelectItems, query.GroupBy)
selectedData := make([]RowType, 0)
if hasAggregateFunctions(query.SelectItems) {
// When can have aggregate functions without GROUP BY clause,
// we should aggregate all rows in that case
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, joinedRows))
} else {
for _, row := range joinedRows {
selectedData = append(selectedData, ctx.selectRow(query.SelectItems, row))
}
}
result = selectedData
}
// Apply distinct // Apply distinct
if query.Distinct { if query.Distinct {
result = deduplicate(result) projectedDocuments = deduplicate(projectedDocuments)
} }
// Apply result limit // Apply result limit
if query.Count > 0 { if query.Count > 0 && len(projectedDocuments) > query.Count {
count := func() int { projectedDocuments = projectedDocuments[:query.Count]
if len(result) < query.Count {
return len(result)
}
return query.Count
}()
result = result[:count]
} }
return result return projectedDocuments
} }
func (c memoryExecutorContext) selectRow(selectItems []parsers.SelectItem, row interface{}) interface{} { func resolveFrom(query parsers.SelectStmt, doc RowType) []rowContext {
// When the first value is top level, select it instead initialRow, gotParentContext := doc.(rowContext)
if len(selectItems) > 0 && selectItems[0].IsTopLevel { if !gotParentContext {
return c.getFieldValue(selectItems[0], row) var initialTableName string
if query.Table.SelectItem.Type == parsers.SelectItemTypeSubQuery {
initialTableName = query.Table.SelectItem.Value.(parsers.SelectStmt).Table.Value
} }
// Construct a new row based on the selected columns if initialTableName == "" {
newRow := make(map[string]interface{}) initialTableName = query.Table.Value
for index, column := range selectItems { }
destinationName := column.Alias
if destinationName == "" { initialRow = rowContext{
if len(column.Path) > 0 { parameters: query.Parameters,
destinationName = column.Path[len(column.Path)-1] tables: map[string]RowType{
} else { initialTableName: doc,
destinationName = fmt.Sprintf("$%d", index+1) },
} }
} }
newRow[destinationName] = c.getFieldValue(column, row) if query.Table.SelectItem.Path != nil || query.Table.SelectItem.Type == parsers.SelectItemTypeSubQuery {
destinationTableName := query.Table.SelectItem.Alias
if destinationTableName == "" {
destinationTableName = query.Table.Value
} }
return newRow selectValue := initialRow.parseArray(query.Table.SelectItem)
rowContexts := make([]rowContext, len(selectValue))
for i, newRowData := range selectValue {
rowContexts[i].parameters = initialRow.parameters
rowContexts[i].tables = copyMap(initialRow.tables)
rowContexts[i].tables[destinationTableName] = newRowData
}
return rowContexts
}
return []rowContext{initialRow}
} }
func (c memoryExecutorContext) evaluateFilters(expr ExpressionType, row RowWithJoins) bool { func (r rowContext) handleJoin(query parsers.SelectStmt) []rowContext {
if expr == nil { currentDocuments := []rowContext{r}
for _, joinItem := range query.JoinItems {
nextDocuments := make([]rowContext, 0)
for _, currentDocument := range currentDocuments {
joinedItems := currentDocument.resolveJoinItemSelect(joinItem.SelectItem)
for _, joinedItem := range joinedItems {
tablesCopy := copyMap(currentDocument.tables)
tablesCopy[joinItem.Table.Value] = joinedItem
nextDocuments = append(nextDocuments, rowContext{
parameters: currentDocument.parameters,
tables: tablesCopy,
})
}
}
currentDocuments = nextDocuments
}
return currentDocuments
}
func (r rowContext) resolveJoinItemSelect(selectItem parsers.SelectItem) []RowType {
if selectItem.Path != nil || selectItem.Type == parsers.SelectItemTypeSubQuery {
selectValue := r.parseArray(selectItem)
documents := make([]RowType, len(selectValue))
for i, newRowData := range selectValue {
documents[i] = newRowData
}
return documents
}
return []RowType{}
}
func (r rowContext) applyFilters(filters interface{}) bool {
if filters == nil {
return true return true
} }
switch typedValue := expr.(type) { switch typedFilters := filters.(type) {
case parsers.ComparisonExpression: case parsers.ComparisonExpression:
leftValue := c.getExpressionParameterValue(typedValue.Left, row) return r.filters_ComparisonExpression(typedFilters)
rightValue := c.getExpressionParameterValue(typedValue.Right, row) case parsers.LogicalExpression:
return r.filters_LogicalExpression(typedFilters)
case parsers.Constant:
if value, ok := typedFilters.Value.(bool); ok {
return value
}
return false
case parsers.SelectItem:
resolvedValue := r.resolveSelectItem(typedFilters)
if value, ok := resolvedValue.(bool); ok {
return value
}
}
return false
}
func (r rowContext) filters_ComparisonExpression(expression parsers.ComparisonExpression) bool {
leftExpression, leftExpressionOk := expression.Left.(parsers.SelectItem)
rightExpression, rightExpressionOk := expression.Right.(parsers.SelectItem)
if !leftExpressionOk || !rightExpressionOk {
logger.Error("ComparisonExpression has incorrect Left or Right type")
return false
}
leftValue := r.resolveSelectItem(leftExpression)
rightValue := r.resolveSelectItem(rightExpression)
cmp := compareValues(leftValue, rightValue) cmp := compareValues(leftValue, rightValue)
switch typedValue.Operation { switch expression.Operation {
case "=": case "=":
return cmp == 0 return cmp == 0
case "!=": case "!=":
@ -157,15 +194,19 @@ func (c memoryExecutorContext) evaluateFilters(expr ExpressionType, row RowWithJ
case ">=": case ">=":
return cmp >= 0 return cmp >= 0
} }
case parsers.LogicalExpression:
return false
}
func (r rowContext) filters_LogicalExpression(expression parsers.LogicalExpression) bool {
var result bool var result bool
for i, expression := range typedValue.Expressions { for i, subExpression := range expression.Expressions {
expressionResult := c.evaluateFilters(expression, row) expressionResult := r.applyFilters(subExpression)
if i == 0 { if i == 0 {
result = expressionResult result = expressionResult
} }
switch typedValue.Operation { switch expression.Operation {
case parsers.LogicalExpressionTypeAnd: case parsers.LogicalExpressionTypeAnd:
result = result && expressionResult result = result && expressionResult
if !result { if !result {
@ -179,242 +220,363 @@ func (c memoryExecutorContext) evaluateFilters(expr ExpressionType, row RowWithJ
} }
} }
return result return result
case parsers.Constant:
if value, ok := typedValue.Value.(bool); ok {
return value
}
return false
case parsers.SelectItem:
resolvedValue := c.getFieldValue(typedValue, row)
if value, ok := resolvedValue.(bool); ok {
return value
}
}
return false
} }
func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row interface{}) interface{} { func applyOrder(documents []rowContext, orderExpressions []parsers.OrderExpression) {
if field.Type == parsers.SelectItemTypeArray { less := func(i, j int) bool {
for _, order := range orderExpressions {
val1 := documents[i].resolveSelectItem(order.SelectItem)
val2 := documents[j].resolveSelectItem(order.SelectItem)
cmp := compareValues(val1, val2)
if cmp != 0 {
if order.Direction == parsers.OrderDirectionDesc {
return cmp > 0
}
return cmp < 0
}
}
return i < j
}
sort.SliceStable(documents, less)
}
func applyGroupBy(documents []rowContext, groupBy []parsers.SelectItem) []rowContext {
groupedRows := make(map[string][]rowContext)
groupedKeys := make([]string, 0)
for _, row := range documents {
key := row.generateGroupByKey(groupBy)
if _, ok := groupedRows[key]; !ok {
groupedKeys = append(groupedKeys, key)
}
groupedRows[key] = append(groupedRows[key], row)
}
grouppedRows := make([]rowContext, 0)
for _, key := range groupedKeys {
grouppedRowContext := rowContext{
tables: groupedRows[key][0].tables,
parameters: groupedRows[key][0].parameters,
grouppedRows: groupedRows[key],
}
grouppedRows = append(grouppedRows, grouppedRowContext)
}
return grouppedRows
}
func (r rowContext) generateGroupByKey(groupBy []parsers.SelectItem) string {
var keyBuilder strings.Builder
for _, selectItem := range groupBy {
value := r.resolveSelectItem(selectItem)
keyBuilder.WriteString(fmt.Sprintf("%v", value))
keyBuilder.WriteString(":")
}
return keyBuilder.String()
}
func applyProjection(documents []rowContext, selectItems []parsers.SelectItem, groupBy []parsers.SelectItem) []RowType {
if len(documents) == 0 {
return []RowType{}
}
if hasAggregateFunctions(selectItems) && len(groupBy) == 0 {
// When can have aggregate functions without GROUP BY clause,
// we should aggregate all rows in that case
rowContext := rowContext{
tables: documents[0].tables,
parameters: documents[0].parameters,
grouppedRows: documents,
}
return []RowType{rowContext.applyProjection(selectItems)}
}
projectedDocuments := make([]RowType, len(documents))
for index, row := range documents {
projectedDocuments[index] = row.applyProjection(selectItems)
}
return projectedDocuments
}
func (r rowContext) applyProjection(selectItems []parsers.SelectItem) RowType {
// When the first value is top level, select it instead
if len(selectItems) > 0 && selectItems[0].IsTopLevel {
return r.resolveSelectItem(selectItems[0])
}
// Construct a new row based on the selected columns
row := make(map[string]interface{})
for index, selectItem := range selectItems {
destinationName := selectItem.Alias
if destinationName == "" {
if len(selectItem.Path) > 0 {
destinationName = selectItem.Path[len(selectItem.Path)-1]
} else {
destinationName = fmt.Sprintf("$%d", index+1)
}
}
row[destinationName] = r.resolveSelectItem(selectItem)
}
return row
}
func (r rowContext) resolveSelectItem(selectItem parsers.SelectItem) interface{} {
if selectItem.Type == parsers.SelectItemTypeArray {
return r.selectItem_SelectItemTypeArray(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeObject {
return r.selectItem_SelectItemTypeObject(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeConstant {
return r.selectItem_SelectItemTypeConstant(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeSubQuery {
return r.selectItem_SelectItemTypeSubQuery(selectItem)
}
if selectItem.Type == parsers.SelectItemTypeFunctionCall {
if typedFunctionCall, ok := selectItem.Value.(parsers.FunctionCall); ok {
return r.selectItem_SelectItemTypeFunctionCall(typedFunctionCall)
}
logger.Error("parsers.SelectItem has incorrect Value type (expected parsers.FunctionCall)")
return nil
}
return r.selectItem_SelectItemTypeField(selectItem)
}
func (r rowContext) selectItem_SelectItemTypeArray(selectItem parsers.SelectItem) interface{} {
arrayValue := make([]interface{}, 0) arrayValue := make([]interface{}, 0)
for _, selectItem := range field.SelectItems { for _, subSelectItem := range selectItem.SelectItems {
arrayValue = append(arrayValue, c.getFieldValue(selectItem, row)) arrayValue = append(arrayValue, r.resolveSelectItem(subSelectItem))
} }
return arrayValue return arrayValue
} }
if field.Type == parsers.SelectItemTypeObject { func (r rowContext) selectItem_SelectItemTypeObject(selectItem parsers.SelectItem) interface{} {
objectValue := make(map[string]interface{}) objectValue := make(map[string]interface{})
for _, selectItem := range field.SelectItems { for _, subSelectItem := range selectItem.SelectItems {
objectValue[selectItem.Alias] = c.getFieldValue(selectItem, row) objectValue[subSelectItem.Alias] = r.resolveSelectItem(subSelectItem)
} }
return objectValue return objectValue
} }
if field.Type == parsers.SelectItemTypeConstant { func (r rowContext) selectItem_SelectItemTypeConstant(selectItem parsers.SelectItem) interface{} {
var typedValue parsers.Constant var typedValue parsers.Constant
var ok bool var ok bool
if typedValue, ok = field.Value.(parsers.Constant); !ok { if typedValue, ok = selectItem.Value.(parsers.Constant); !ok {
// TODO: Handle error // TODO: Handle error
logger.Error("parsers.Constant has incorrect Value type") logger.Error("parsers.Constant has incorrect Value type")
} }
if typedValue.Type == parsers.ConstantTypeParameterConstant && if typedValue.Type == parsers.ConstantTypeParameterConstant &&
c.parameters != nil { r.parameters != nil {
if key, ok := typedValue.Value.(string); ok { if key, ok := typedValue.Value.(string); ok {
return c.parameters[key] return r.parameters[key]
} }
} }
return typedValue.Value return typedValue.Value
}
func (r rowContext) selectItem_SelectItemTypeSubQuery(selectItem parsers.SelectItem) interface{} {
subQuery := selectItem.Value.(parsers.SelectStmt)
subQueryResult := ExecuteQuery(
subQuery,
[]RowType{r},
)
if subQuery.Exists {
return len(subQueryResult) > 0
} }
rowValue := row return subQueryResult
// Used for aggregates }
if array, isArray := row.([]RowWithJoins); isArray {
rowValue = array[0]
}
if field.Type == parsers.SelectItemTypeFunctionCall { func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.FunctionCall) interface{} {
var typedValue parsers.FunctionCall switch functionCall.Type {
var ok bool
if typedValue, ok = field.Value.(parsers.FunctionCall); !ok {
// TODO: Handle error
logger.Error("parsers.Constant has incorrect Value type")
}
switch typedValue.Type {
case parsers.FunctionCallStringEquals: case parsers.FunctionCallStringEquals:
return c.strings_StringEquals(typedValue.Arguments, rowValue) return r.strings_StringEquals(functionCall.Arguments)
case parsers.FunctionCallContains: case parsers.FunctionCallContains:
return c.strings_Contains(typedValue.Arguments, rowValue) return r.strings_Contains(functionCall.Arguments)
case parsers.FunctionCallEndsWith: case parsers.FunctionCallEndsWith:
return c.strings_EndsWith(typedValue.Arguments, rowValue) return r.strings_EndsWith(functionCall.Arguments)
case parsers.FunctionCallStartsWith: case parsers.FunctionCallStartsWith:
return c.strings_StartsWith(typedValue.Arguments, rowValue) return r.strings_StartsWith(functionCall.Arguments)
case parsers.FunctionCallConcat: case parsers.FunctionCallConcat:
return c.strings_Concat(typedValue.Arguments, rowValue) return r.strings_Concat(functionCall.Arguments)
case parsers.FunctionCallIndexOf: case parsers.FunctionCallIndexOf:
return c.strings_IndexOf(typedValue.Arguments, rowValue) return r.strings_IndexOf(functionCall.Arguments)
case parsers.FunctionCallToString: case parsers.FunctionCallToString:
return c.strings_ToString(typedValue.Arguments, rowValue) return r.strings_ToString(functionCall.Arguments)
case parsers.FunctionCallUpper: case parsers.FunctionCallUpper:
return c.strings_Upper(typedValue.Arguments, rowValue) return r.strings_Upper(functionCall.Arguments)
case parsers.FunctionCallLower: case parsers.FunctionCallLower:
return c.strings_Lower(typedValue.Arguments, rowValue) return r.strings_Lower(functionCall.Arguments)
case parsers.FunctionCallLeft: case parsers.FunctionCallLeft:
return c.strings_Left(typedValue.Arguments, rowValue) return r.strings_Left(functionCall.Arguments)
case parsers.FunctionCallLength: case parsers.FunctionCallLength:
return c.strings_Length(typedValue.Arguments, rowValue) return r.strings_Length(functionCall.Arguments)
case parsers.FunctionCallLTrim: case parsers.FunctionCallLTrim:
return c.strings_LTrim(typedValue.Arguments, rowValue) return r.strings_LTrim(functionCall.Arguments)
case parsers.FunctionCallReplace: case parsers.FunctionCallReplace:
return c.strings_Replace(typedValue.Arguments, rowValue) return r.strings_Replace(functionCall.Arguments)
case parsers.FunctionCallReplicate: case parsers.FunctionCallReplicate:
return c.strings_Replicate(typedValue.Arguments, rowValue) return r.strings_Replicate(functionCall.Arguments)
case parsers.FunctionCallReverse: case parsers.FunctionCallReverse:
return c.strings_Reverse(typedValue.Arguments, rowValue) return r.strings_Reverse(functionCall.Arguments)
case parsers.FunctionCallRight: case parsers.FunctionCallRight:
return c.strings_Right(typedValue.Arguments, rowValue) return r.strings_Right(functionCall.Arguments)
case parsers.FunctionCallRTrim: case parsers.FunctionCallRTrim:
return c.strings_RTrim(typedValue.Arguments, rowValue) return r.strings_RTrim(functionCall.Arguments)
case parsers.FunctionCallSubstring: case parsers.FunctionCallSubstring:
return c.strings_Substring(typedValue.Arguments, rowValue) return r.strings_Substring(functionCall.Arguments)
case parsers.FunctionCallTrim: case parsers.FunctionCallTrim:
return c.strings_Trim(typedValue.Arguments, rowValue) return r.strings_Trim(functionCall.Arguments)
case parsers.FunctionCallIsDefined: case parsers.FunctionCallIsDefined:
return c.typeChecking_IsDefined(typedValue.Arguments, rowValue) return r.typeChecking_IsDefined(functionCall.Arguments)
case parsers.FunctionCallIsArray: case parsers.FunctionCallIsArray:
return c.typeChecking_IsArray(typedValue.Arguments, rowValue) return r.typeChecking_IsArray(functionCall.Arguments)
case parsers.FunctionCallIsBool: case parsers.FunctionCallIsBool:
return c.typeChecking_IsBool(typedValue.Arguments, rowValue) return r.typeChecking_IsBool(functionCall.Arguments)
case parsers.FunctionCallIsFiniteNumber: case parsers.FunctionCallIsFiniteNumber:
return c.typeChecking_IsFiniteNumber(typedValue.Arguments, rowValue) return r.typeChecking_IsFiniteNumber(functionCall.Arguments)
case parsers.FunctionCallIsInteger: case parsers.FunctionCallIsInteger:
return c.typeChecking_IsInteger(typedValue.Arguments, rowValue) return r.typeChecking_IsInteger(functionCall.Arguments)
case parsers.FunctionCallIsNull: case parsers.FunctionCallIsNull:
return c.typeChecking_IsNull(typedValue.Arguments, rowValue) return r.typeChecking_IsNull(functionCall.Arguments)
case parsers.FunctionCallIsNumber: case parsers.FunctionCallIsNumber:
return c.typeChecking_IsNumber(typedValue.Arguments, rowValue) return r.typeChecking_IsNumber(functionCall.Arguments)
case parsers.FunctionCallIsObject: case parsers.FunctionCallIsObject:
return c.typeChecking_IsObject(typedValue.Arguments, rowValue) return r.typeChecking_IsObject(functionCall.Arguments)
case parsers.FunctionCallIsPrimitive: case parsers.FunctionCallIsPrimitive:
return c.typeChecking_IsPrimitive(typedValue.Arguments, rowValue) return r.typeChecking_IsPrimitive(functionCall.Arguments)
case parsers.FunctionCallIsString: case parsers.FunctionCallIsString:
return c.typeChecking_IsString(typedValue.Arguments, rowValue) return r.typeChecking_IsString(functionCall.Arguments)
case parsers.FunctionCallArrayConcat: case parsers.FunctionCallArrayConcat:
return c.array_Concat(typedValue.Arguments, rowValue) return r.array_Concat(functionCall.Arguments)
case parsers.FunctionCallArrayLength: case parsers.FunctionCallArrayLength:
return c.array_Length(typedValue.Arguments, rowValue) return r.array_Length(functionCall.Arguments)
case parsers.FunctionCallArraySlice: case parsers.FunctionCallArraySlice:
return c.array_Slice(typedValue.Arguments, rowValue) return r.array_Slice(functionCall.Arguments)
case parsers.FunctionCallSetIntersect: case parsers.FunctionCallSetIntersect:
return c.set_Intersect(typedValue.Arguments, rowValue) return r.set_Intersect(functionCall.Arguments)
case parsers.FunctionCallSetUnion: case parsers.FunctionCallSetUnion:
return c.set_Union(typedValue.Arguments, rowValue) return r.set_Union(functionCall.Arguments)
case parsers.FunctionCallMathAbs: case parsers.FunctionCallMathAbs:
return c.math_Abs(typedValue.Arguments, rowValue) return r.math_Abs(functionCall.Arguments)
case parsers.FunctionCallMathAcos: case parsers.FunctionCallMathAcos:
return c.math_Acos(typedValue.Arguments, rowValue) return r.math_Acos(functionCall.Arguments)
case parsers.FunctionCallMathAsin: case parsers.FunctionCallMathAsin:
return c.math_Asin(typedValue.Arguments, rowValue) return r.math_Asin(functionCall.Arguments)
case parsers.FunctionCallMathAtan: case parsers.FunctionCallMathAtan:
return c.math_Atan(typedValue.Arguments, rowValue) return r.math_Atan(functionCall.Arguments)
case parsers.FunctionCallMathCeiling: case parsers.FunctionCallMathCeiling:
return c.math_Ceiling(typedValue.Arguments, rowValue) return r.math_Ceiling(functionCall.Arguments)
case parsers.FunctionCallMathCos: case parsers.FunctionCallMathCos:
return c.math_Cos(typedValue.Arguments, rowValue) return r.math_Cos(functionCall.Arguments)
case parsers.FunctionCallMathCot: case parsers.FunctionCallMathCot:
return c.math_Cot(typedValue.Arguments, rowValue) return r.math_Cot(functionCall.Arguments)
case parsers.FunctionCallMathDegrees: case parsers.FunctionCallMathDegrees:
return c.math_Degrees(typedValue.Arguments, rowValue) return r.math_Degrees(functionCall.Arguments)
case parsers.FunctionCallMathExp: case parsers.FunctionCallMathExp:
return c.math_Exp(typedValue.Arguments, rowValue) return r.math_Exp(functionCall.Arguments)
case parsers.FunctionCallMathFloor: case parsers.FunctionCallMathFloor:
return c.math_Floor(typedValue.Arguments, rowValue) return r.math_Floor(functionCall.Arguments)
case parsers.FunctionCallMathIntBitNot: case parsers.FunctionCallMathIntBitNot:
return c.math_IntBitNot(typedValue.Arguments, rowValue) return r.math_IntBitNot(functionCall.Arguments)
case parsers.FunctionCallMathLog10: case parsers.FunctionCallMathLog10:
return c.math_Log10(typedValue.Arguments, rowValue) return r.math_Log10(functionCall.Arguments)
case parsers.FunctionCallMathRadians: case parsers.FunctionCallMathRadians:
return c.math_Radians(typedValue.Arguments, rowValue) return r.math_Radians(functionCall.Arguments)
case parsers.FunctionCallMathRound: case parsers.FunctionCallMathRound:
return c.math_Round(typedValue.Arguments, rowValue) return r.math_Round(functionCall.Arguments)
case parsers.FunctionCallMathSign: case parsers.FunctionCallMathSign:
return c.math_Sign(typedValue.Arguments, rowValue) return r.math_Sign(functionCall.Arguments)
case parsers.FunctionCallMathSin: case parsers.FunctionCallMathSin:
return c.math_Sin(typedValue.Arguments, rowValue) return r.math_Sin(functionCall.Arguments)
case parsers.FunctionCallMathSqrt: case parsers.FunctionCallMathSqrt:
return c.math_Sqrt(typedValue.Arguments, rowValue) return r.math_Sqrt(functionCall.Arguments)
case parsers.FunctionCallMathSquare: case parsers.FunctionCallMathSquare:
return c.math_Square(typedValue.Arguments, rowValue) return r.math_Square(functionCall.Arguments)
case parsers.FunctionCallMathTan: case parsers.FunctionCallMathTan:
return c.math_Tan(typedValue.Arguments, rowValue) return r.math_Tan(functionCall.Arguments)
case parsers.FunctionCallMathTrunc: case parsers.FunctionCallMathTrunc:
return c.math_Trunc(typedValue.Arguments, rowValue) return r.math_Trunc(functionCall.Arguments)
case parsers.FunctionCallMathAtn2: case parsers.FunctionCallMathAtn2:
return c.math_Atn2(typedValue.Arguments, rowValue) return r.math_Atn2(functionCall.Arguments)
case parsers.FunctionCallMathIntAdd: case parsers.FunctionCallMathIntAdd:
return c.math_IntAdd(typedValue.Arguments, rowValue) return r.math_IntAdd(functionCall.Arguments)
case parsers.FunctionCallMathIntBitAnd: case parsers.FunctionCallMathIntBitAnd:
return c.math_IntBitAnd(typedValue.Arguments, rowValue) return r.math_IntBitAnd(functionCall.Arguments)
case parsers.FunctionCallMathIntBitLeftShift: case parsers.FunctionCallMathIntBitLeftShift:
return c.math_IntBitLeftShift(typedValue.Arguments, rowValue) return r.math_IntBitLeftShift(functionCall.Arguments)
case parsers.FunctionCallMathIntBitOr: case parsers.FunctionCallMathIntBitOr:
return c.math_IntBitOr(typedValue.Arguments, rowValue) return r.math_IntBitOr(functionCall.Arguments)
case parsers.FunctionCallMathIntBitRightShift: case parsers.FunctionCallMathIntBitRightShift:
return c.math_IntBitRightShift(typedValue.Arguments, rowValue) return r.math_IntBitRightShift(functionCall.Arguments)
case parsers.FunctionCallMathIntBitXor: case parsers.FunctionCallMathIntBitXor:
return c.math_IntBitXor(typedValue.Arguments, rowValue) return r.math_IntBitXor(functionCall.Arguments)
case parsers.FunctionCallMathIntDiv: case parsers.FunctionCallMathIntDiv:
return c.math_IntDiv(typedValue.Arguments, rowValue) return r.math_IntDiv(functionCall.Arguments)
case parsers.FunctionCallMathIntMod: case parsers.FunctionCallMathIntMod:
return c.math_IntMod(typedValue.Arguments, rowValue) return r.math_IntMod(functionCall.Arguments)
case parsers.FunctionCallMathIntMul: case parsers.FunctionCallMathIntMul:
return c.math_IntMul(typedValue.Arguments, rowValue) return r.math_IntMul(functionCall.Arguments)
case parsers.FunctionCallMathIntSub: case parsers.FunctionCallMathIntSub:
return c.math_IntSub(typedValue.Arguments, rowValue) return r.math_IntSub(functionCall.Arguments)
case parsers.FunctionCallMathPower: case parsers.FunctionCallMathPower:
return c.math_Power(typedValue.Arguments, rowValue) return r.math_Power(functionCall.Arguments)
case parsers.FunctionCallMathLog: case parsers.FunctionCallMathLog:
return c.math_Log(typedValue.Arguments, rowValue) return r.math_Log(functionCall.Arguments)
case parsers.FunctionCallMathNumberBin: case parsers.FunctionCallMathNumberBin:
return c.math_NumberBin(typedValue.Arguments, rowValue) return r.math_NumberBin(functionCall.Arguments)
case parsers.FunctionCallMathPi: case parsers.FunctionCallMathPi:
return c.math_Pi() return r.math_Pi()
case parsers.FunctionCallMathRand: case parsers.FunctionCallMathRand:
return c.math_Rand() return r.math_Rand()
case parsers.FunctionCallAggregateAvg: case parsers.FunctionCallAggregateAvg:
return c.aggregate_Avg(typedValue.Arguments, row) return r.aggregate_Avg(functionCall.Arguments)
case parsers.FunctionCallAggregateCount: case parsers.FunctionCallAggregateCount:
return c.aggregate_Count(typedValue.Arguments, row) return r.aggregate_Count(functionCall.Arguments)
case parsers.FunctionCallAggregateMax: case parsers.FunctionCallAggregateMax:
return c.aggregate_Max(typedValue.Arguments, row) return r.aggregate_Max(functionCall.Arguments)
case parsers.FunctionCallAggregateMin: case parsers.FunctionCallAggregateMin:
return c.aggregate_Min(typedValue.Arguments, row) return r.aggregate_Min(functionCall.Arguments)
case parsers.FunctionCallAggregateSum: case parsers.FunctionCallAggregateSum:
return c.aggregate_Sum(typedValue.Arguments, row) return r.aggregate_Sum(functionCall.Arguments)
case parsers.FunctionCallIn: case parsers.FunctionCallIn:
return c.misc_In(typedValue.Arguments, rowValue) return r.misc_In(functionCall.Arguments)
}
} }
value := rowValue logger.Errorf("Unknown function call type: %v", functionCall.Type)
if joinedRow, isRowWithJoins := value.(RowWithJoins); isRowWithJoins { return nil
value = joinedRow[field.Path[0]] }
}
if len(field.Path) > 1 { func (r rowContext) selectItem_SelectItemTypeField(selectItem parsers.SelectItem) interface{} {
for _, pathSegment := range field.Path[1:] { value := r.tables[selectItem.Path[0]]
if len(selectItem.Path) > 1 {
for _, pathSegment := range selectItem.Path[1:] {
switch nestedValue := value.(type) { switch nestedValue := value.(type) {
case map[string]interface{}: case map[string]interface{}:
value = nestedValue[pathSegment] value = nestedValue[pathSegment]
case RowWithJoins: case map[string]RowType:
value = nestedValue[pathSegment] value = nestedValue[pathSegment]
case []int, []string, []interface{}: case []int, []string, []interface{}:
slice := reflect.ValueOf(nestedValue) slice := reflect.ValueOf(nestedValue)
@ -428,82 +590,28 @@ func (c memoryExecutorContext) getFieldValue(field parsers.SelectItem, row inter
} }
} }
} }
return value return value
} }
func (c memoryExecutorContext) getExpressionParameterValue( func hasAggregateFunctions(selectItems []parsers.SelectItem) bool {
parameter interface{}, if selectItems == nil {
row RowWithJoins, return false
) interface{} {
switch typedParameter := parameter.(type) {
case parsers.SelectItem:
return c.getFieldValue(typedParameter, row)
} }
logger.Error("getExpressionParameterValue - got incorrect parameter type") for _, selectItem := range selectItems {
if selectItem.Type == parsers.SelectItemTypeFunctionCall {
return nil if typedValue, ok := selectItem.Value.(parsers.FunctionCall); ok && slices.Contains[[]parsers.FunctionCallType](parsers.AggregateFunctions, typedValue.Type) {
} return true
func (c memoryExecutorContext) orderBy(orderBy []parsers.OrderExpression, data []RowWithJoins) {
less := func(i, j int) bool {
for _, order := range orderBy {
val1 := c.getFieldValue(order.SelectItem, data[i])
val2 := c.getFieldValue(order.SelectItem, data[j])
cmp := compareValues(val1, val2)
if cmp != 0 {
if order.Direction == parsers.OrderDirectionDesc {
return cmp > 0
}
return cmp < 0
} }
} }
return i < j
if hasAggregateFunctions(selectItem.SelectItems) {
return true
}
} }
sort.SliceStable(data, less) return false
}
func (c memoryExecutorContext) groupBy(selectStmt parsers.SelectStmt, data []RowWithJoins) []RowType {
groupedRows := make(map[string][]RowWithJoins)
groupedKeys := make([]string, 0)
// Group rows by group by columns
for _, row := range data {
key := c.generateGroupKey(selectStmt.GroupBy, row)
if _, ok := groupedRows[key]; !ok {
groupedKeys = append(groupedKeys, key)
}
groupedRows[key] = append(groupedRows[key], row)
}
// Aggregate each group
aggregatedRows := make([]RowType, 0)
for _, key := range groupedKeys {
groupRows := groupedRows[key]
aggregatedRow := c.aggregateGroup(selectStmt, groupRows)
aggregatedRows = append(aggregatedRows, aggregatedRow)
}
return aggregatedRows
}
func (c memoryExecutorContext) generateGroupKey(groupByFields []parsers.SelectItem, row RowWithJoins) string {
var keyBuilder strings.Builder
for _, column := range groupByFields {
fieldValue := c.getFieldValue(column, row)
keyBuilder.WriteString(fmt.Sprintf("%v", fieldValue))
keyBuilder.WriteString(":")
}
return keyBuilder.String()
}
func (c memoryExecutorContext) aggregateGroup(selectStmt parsers.SelectStmt, groupRows []RowWithJoins) RowType {
aggregatedRow := c.selectRow(selectStmt.SelectItems, groupRows)
return aggregatedRow
} }
func compareValues(val1, val2 interface{}) int { func compareValues(val1, val2 interface{}) int {
@ -549,8 +657,8 @@ func compareValues(val1, val2 interface{}) int {
} }
} }
func deduplicate(slice []RowType) []RowType { func deduplicate[T RowType | interface{}](slice []T) []T {
var result []RowType var result []T
for i := 0; i < len(slice); i++ { for i := 0; i < len(slice); i++ {
unique := true unique := true
@ -569,42 +677,8 @@ func deduplicate(slice []RowType) []RowType {
return result return result
} }
func hasAggregateFunctions(selectItems []parsers.SelectItem) bool { func copyMap[T RowType | []RowType](originalMap map[string]T) map[string]T {
if selectItems == nil { targetMap := make(map[string]T)
return false
}
for _, selectItem := range selectItems {
if selectItem.Type == parsers.SelectItemTypeFunctionCall {
if typedValue, ok := selectItem.Value.(parsers.FunctionCall); ok && slices.Contains[[]parsers.FunctionCallType](parsers.AggregateFunctions, typedValue.Type) {
return true
}
}
if hasAggregateFunctions(selectItem.SelectItems) {
return true
}
}
return false
}
func zipRows(current []RowWithJoins, joinedTableName string, rowsToZip []RowType) []RowWithJoins {
resultMap := make([]RowWithJoins, 0)
for _, currentRow := range current {
for _, rowToZip := range rowsToZip {
newRow := copyMap(currentRow)
newRow[joinedTableName] = rowToZip
resultMap = append(resultMap, newRow)
}
}
return resultMap
}
func copyMap(originalMap map[string]RowType) map[string]RowType {
targetMap := make(map[string]RowType)
for k, v := range originalMap { for k, v := range originalMap {
targetMap[k] = v targetMap[k] = v

View File

@ -4,11 +4,11 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) misc_In(arguments []interface{}, row RowType) bool { func (r rowContext) misc_In(arguments []interface{}) bool {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
for i := 1; i < len(arguments); i++ { for i := 1; i < len(arguments); i++ {
compareValue := c.getFieldValue(arguments[i].(parsers.SelectItem), row) compareValue := r.resolveSelectItem(arguments[i].(parsers.SelectItem))
if compareValues(value, compareValue) == 0 { if compareValues(value, compareValue) == 0 {
return true return true
} }

View File

@ -14,7 +14,7 @@ func testQueryExecute(
data []memoryexecutor.RowType, data []memoryexecutor.RowType,
expectedData []memoryexecutor.RowType, expectedData []memoryexecutor.RowType,
) { ) {
result := memoryexecutor.Execute(query, data) result := memoryexecutor.ExecuteQuery(query, data)
if !reflect.DeepEqual(result, expectedData) { if !reflect.DeepEqual(result, expectedData) {
t.Errorf("execution result does not match expected data.\nExpected: %+v\nGot: %+v", expectedData, result) t.Errorf("execution result does not match expected data.\nExpected: %+v\nGot: %+v", expectedData, result)
@ -25,8 +25,20 @@ func Test_Execute(t *testing.T) {
mockData := []memoryexecutor.RowType{ mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "12345", "pk": 123, "_self": "self1", "_rid": "rid1", "_ts": 123456, "isCool": false}, 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}, map[string]interface{}{"id": "67890", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true},
map[string]interface{}{"id": "456", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true}, map[string]interface{}{
map[string]interface{}{"id": "123", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true}, "id": "456", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true,
"tags": []map[string]interface{}{
{"name": "tag-a"},
{"name": "tag-b"},
},
},
map[string]interface{}{
"id": "123", "pk": 456, "_self": "self2", "_rid": "rid2", "_ts": 789012, "isCool": true,
"tags": []map[string]interface{}{
{"name": "tag-b"},
{"name": "tag-c"},
},
},
} }
t.Run("Should execute SELECT with ORDER BY", func(t *testing.T) { t.Run("Should execute SELECT with ORDER BY", func(t *testing.T) {
@ -124,4 +136,31 @@ func Test_Execute(t *testing.T) {
}, },
) )
}) })
t.Run("Should execute IN selector", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "name"},
Type: parsers.SelectItemTypeField,
},
},
Table: parsers.Table{
Value: "c",
SelectItem: parsers.SelectItem{
Path: []string{"c", "tags"},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"name": "tag-a"},
map[string]interface{}{"name": "tag-b"},
map[string]interface{}{"name": "tag-b"},
map[string]interface{}{"name": "tag-c"},
},
)
})
} }

View File

@ -8,10 +8,10 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) strings_StringEquals(arguments []interface{}, row RowType) bool { func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
return strings.EqualFold(str1, str2) return strings.EqualFold(str1, str2)
@ -20,10 +20,10 @@ func (c memoryExecutorContext) strings_StringEquals(arguments []interface{}, row
return str1 == str2 return str1 == str2
} }
func (c memoryExecutorContext) strings_Contains(arguments []interface{}, row RowType) bool { func (r rowContext) strings_Contains(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
@ -33,10 +33,10 @@ func (c memoryExecutorContext) strings_Contains(arguments []interface{}, row Row
return strings.Contains(str1, str2) return strings.Contains(str1, str2)
} }
func (c memoryExecutorContext) strings_EndsWith(arguments []interface{}, row RowType) bool { func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
@ -46,10 +46,10 @@ func (c memoryExecutorContext) strings_EndsWith(arguments []interface{}, row Row
return strings.HasSuffix(str1, str2) return strings.HasSuffix(str1, str2)
} }
func (c memoryExecutorContext) strings_StartsWith(arguments []interface{}, row RowType) bool { func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
ignoreCase := c.getBoolFlag(arguments, row) ignoreCase := r.getBoolFlag(arguments)
if ignoreCase { if ignoreCase {
str1 = strings.ToLower(str1) str1 = strings.ToLower(str1)
@ -59,12 +59,12 @@ func (c memoryExecutorContext) strings_StartsWith(arguments []interface{}, row R
return strings.HasPrefix(str1, str2) return strings.HasPrefix(str1, str2)
} }
func (c memoryExecutorContext) strings_Concat(arguments []interface{}, row RowType) string { func (r rowContext) strings_Concat(arguments []interface{}) string {
result := "" result := ""
for _, arg := range arguments { for _, arg := range arguments {
if selectItem, ok := arg.(parsers.SelectItem); ok { if selectItem, ok := arg.(parsers.SelectItem); ok {
value := c.getFieldValue(selectItem, row) value := r.resolveSelectItem(selectItem)
result += convertToString(value) result += convertToString(value)
} }
} }
@ -72,13 +72,13 @@ func (c memoryExecutorContext) strings_Concat(arguments []interface{}, row RowTy
return result return result
} }
func (c memoryExecutorContext) strings_IndexOf(arguments []interface{}, row RowType) int { func (r rowContext) strings_IndexOf(arguments []interface{}) int {
str1 := c.parseString(arguments[0], row) str1 := r.parseString(arguments[0])
str2 := c.parseString(arguments[1], row) str2 := r.parseString(arguments[1])
start := 0 start := 0
if len(arguments) > 2 && arguments[2] != nil { if len(arguments) > 2 && arguments[2] != nil {
if startPos, ok := c.getFieldValue(arguments[2].(parsers.SelectItem), row).(int); ok { if startPos, ok := r.resolveSelectItem(arguments[2].(parsers.SelectItem)).(int); ok {
start = startPos start = startPos
} }
} }
@ -97,26 +97,26 @@ func (c memoryExecutorContext) strings_IndexOf(arguments []interface{}, row RowT
} }
} }
func (c memoryExecutorContext) strings_ToString(arguments []interface{}, row RowType) string { func (r rowContext) strings_ToString(arguments []interface{}) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return convertToString(value) return convertToString(value)
} }
func (c memoryExecutorContext) strings_Upper(arguments []interface{}, row RowType) string { func (r rowContext) strings_Upper(arguments []interface{}) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return strings.ToUpper(convertToString(value)) return strings.ToUpper(convertToString(value))
} }
func (c memoryExecutorContext) strings_Lower(arguments []interface{}, row RowType) string { func (r rowContext) strings_Lower(arguments []interface{}) string {
value := c.getFieldValue(arguments[0].(parsers.SelectItem), row) value := r.resolveSelectItem(arguments[0].(parsers.SelectItem))
return strings.ToLower(convertToString(value)) return strings.ToLower(convertToString(value))
} }
func (c memoryExecutorContext) strings_Left(arguments []interface{}, row RowType) string { func (r rowContext) strings_Left(arguments []interface{}) string {
var ok bool var ok bool
var length int var length int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Left - got parameters of wrong type") logger.Error("strings_Left - got parameters of wrong type")
@ -134,28 +134,28 @@ func (c memoryExecutorContext) strings_Left(arguments []interface{}, row RowType
return str[:length] return str[:length]
} }
func (c memoryExecutorContext) strings_Length(arguments []interface{}, row RowType) int { func (r rowContext) strings_Length(arguments []interface{}) int {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return len(str) return len(str)
} }
func (c memoryExecutorContext) strings_LTrim(arguments []interface{}, row RowType) string { func (r rowContext) strings_LTrim(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return strings.TrimLeft(str, " ") return strings.TrimLeft(str, " ")
} }
func (c memoryExecutorContext) strings_Replace(arguments []interface{}, row RowType) string { func (r rowContext) strings_Replace(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
oldStr := c.parseString(arguments[1], row) oldStr := r.parseString(arguments[1])
newStr := c.parseString(arguments[2], row) newStr := r.parseString(arguments[2])
return strings.Replace(str, oldStr, newStr, -1) return strings.Replace(str, oldStr, newStr, -1)
} }
func (c memoryExecutorContext) strings_Replicate(arguments []interface{}, row RowType) string { func (r rowContext) strings_Replicate(arguments []interface{}) string {
var ok bool var ok bool
var times int var times int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
timesEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if times, ok = timesEx.(int); !ok { if times, ok = timesEx.(int); !ok {
logger.Error("strings_Replicate - got parameters of wrong type") logger.Error("strings_Replicate - got parameters of wrong type")
@ -173,8 +173,8 @@ func (c memoryExecutorContext) strings_Replicate(arguments []interface{}, row Ro
return strings.Repeat(str, times) return strings.Repeat(str, times)
} }
func (c memoryExecutorContext) strings_Reverse(arguments []interface{}, row RowType) string { func (r rowContext) strings_Reverse(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
runes := []rune(str) runes := []rune(str)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
@ -184,11 +184,11 @@ func (c memoryExecutorContext) strings_Reverse(arguments []interface{}, row RowT
return string(runes) return string(runes)
} }
func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowType) string { func (r rowContext) strings_Right(arguments []interface{}) string {
var ok bool var ok bool
var length int var length int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
lengthEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok { if length, ok = lengthEx.(int); !ok {
logger.Error("strings_Right - got parameters of wrong type") logger.Error("strings_Right - got parameters of wrong type")
@ -206,18 +206,18 @@ func (c memoryExecutorContext) strings_Right(arguments []interface{}, row RowTyp
return str[len(str)-length:] return str[len(str)-length:]
} }
func (c memoryExecutorContext) strings_RTrim(arguments []interface{}, row RowType) string { func (r rowContext) strings_RTrim(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return strings.TrimRight(str, " ") return strings.TrimRight(str, " ")
} }
func (c memoryExecutorContext) strings_Substring(arguments []interface{}, row RowType) string { func (r rowContext) strings_Substring(arguments []interface{}) string {
var ok bool var ok bool
var startPos int var startPos int
var length int var length int
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
startPosEx := c.getFieldValue(arguments[1].(parsers.SelectItem), row) startPosEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
lengthEx := c.getFieldValue(arguments[2].(parsers.SelectItem), row) lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if startPos, ok = startPosEx.(int); !ok { if startPos, ok = startPosEx.(int); !ok {
logger.Error("strings_Substring - got start parameters of wrong type") logger.Error("strings_Substring - got start parameters of wrong type")
@ -240,16 +240,16 @@ func (c memoryExecutorContext) strings_Substring(arguments []interface{}, row Ro
return str[startPos:endPos] return str[startPos:endPos]
} }
func (c memoryExecutorContext) strings_Trim(arguments []interface{}, row RowType) string { func (r rowContext) strings_Trim(arguments []interface{}) string {
str := c.parseString(arguments[0], row) str := r.parseString(arguments[0])
return strings.TrimSpace(str) return strings.TrimSpace(str)
} }
func (c memoryExecutorContext) getBoolFlag(arguments []interface{}, row RowType) bool { func (r rowContext) getBoolFlag(arguments []interface{}) bool {
ignoreCase := false ignoreCase := false
if len(arguments) > 2 && arguments[2] != nil { if len(arguments) > 2 && arguments[2] != nil {
ignoreCaseItem := arguments[2].(parsers.SelectItem) ignoreCaseItem := arguments[2].(parsers.SelectItem)
if value, ok := c.getFieldValue(ignoreCaseItem, row).(bool); ok { if value, ok := r.resolveSelectItem(ignoreCaseItem).(bool); ok {
ignoreCase = value ignoreCase = value
} }
} }
@ -257,9 +257,9 @@ func (c memoryExecutorContext) getBoolFlag(arguments []interface{}, row RowType)
return ignoreCase return ignoreCase
} }
func (c memoryExecutorContext) parseString(argument interface{}, row RowType) string { func (r rowContext) parseString(argument interface{}) string {
exItem := argument.(parsers.SelectItem) exItem := argument.(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
if str1, ok := ex.(string); ok { if str1, ok := ex.(string); ok {
return str1 return str1
} }

View File

@ -0,0 +1,155 @@
package memoryexecutor_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
)
func Test_Execute_SubQuery(t *testing.T) {
mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "123", "info": map[string]interface{}{"name": "row-1"}},
map[string]interface{}{
"id": "456",
"info": map[string]interface{}{"name": "row-2"},
"tags": []map[string]interface{}{
{"name": "tag-a"},
{"name": "tag-b"},
},
},
map[string]interface{}{
"id": "789",
"info": map[string]interface{}{"name": "row-3"},
"tags": []map[string]interface{}{
{"name": "tag-b"},
{"name": "tag-c"},
},
},
}
t.Run("Should execute FROM subquery", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "name"}},
},
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},
},
},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"name": "row-1"},
map[string]interface{}{"name": "row-2"},
map[string]interface{}{"name": "row-3"},
},
)
})
t.Run("Should execute JOIN subquery", func(t *testing.T) {
testQueryExecute(
t,
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"},
},
},
},
},
},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "456", "name": "tag-a"},
map[string]interface{}{"id": "456", "name": "tag-b"},
map[string]interface{}{"id": "789", "name": "tag-b"},
map[string]interface{}{"id": "789", "name": "tag-c"},
},
)
})
t.Run("Should execute JOIN EXISTS subquery", func(t *testing.T) {
testQueryExecute(
t,
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"},
},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "456"},
map[string]interface{}{"id": "789"},
},
)
})
}

View File

@ -6,32 +6,32 @@ import (
"github.com/pikami/cosmium/parsers" "github.com/pikami/cosmium/parsers"
) )
func (c memoryExecutorContext) typeChecking_IsDefined(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsDefined(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
return ex != nil return ex != nil
} }
func (c memoryExecutorContext) typeChecking_IsArray(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsArray(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isArray := ex.([]interface{}) _, isArray := ex.([]interface{})
return isArray return isArray
} }
func (c memoryExecutorContext) typeChecking_IsBool(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsBool(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isBool := ex.(bool) _, isBool := ex.(bool)
return isBool return isBool
} }
func (c memoryExecutorContext) typeChecking_IsFiniteNumber(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsFiniteNumber(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch num := ex.(type) { switch num := ex.(type) {
case int: case int:
@ -43,41 +43,41 @@ func (c memoryExecutorContext) typeChecking_IsFiniteNumber(arguments []interface
} }
} }
func (c memoryExecutorContext) typeChecking_IsInteger(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsInteger(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isInt := ex.(int) _, isInt := ex.(int)
return isInt return isInt
} }
func (c memoryExecutorContext) typeChecking_IsNull(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsNull(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
return ex == nil return ex == nil
} }
func (c memoryExecutorContext) typeChecking_IsNumber(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsNumber(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isFloat := ex.(float64) _, isFloat := ex.(float64)
_, isInt := ex.(int) _, isInt := ex.(int)
return isFloat || isInt return isFloat || isInt
} }
func (c memoryExecutorContext) typeChecking_IsObject(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsObject(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isObject := ex.(map[string]interface{}) _, isObject := ex.(map[string]interface{})
return isObject return isObject
} }
func (c memoryExecutorContext) typeChecking_IsPrimitive(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsPrimitive(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
switch ex.(type) { switch ex.(type) {
case bool, string, float64, int, nil: case bool, string, float64, int, nil:
@ -87,9 +87,9 @@ func (c memoryExecutorContext) typeChecking_IsPrimitive(arguments []interface{},
} }
} }
func (c memoryExecutorContext) typeChecking_IsString(arguments []interface{}, row RowType) bool { func (r rowContext) typeChecking_IsString(arguments []interface{}) bool {
exItem := arguments[0].(parsers.SelectItem) exItem := arguments[0].(parsers.SelectItem)
ex := c.getFieldValue(exItem, row) ex := r.resolveSelectItem(exItem)
_, isStr := ex.(string) _, isStr := ex.(string)
return isStr return isStr