provide mechanism to call functions when doing aggregations.

provide a standardized format for token aggregation ($.system|$.code)
This commit is contained in:
Jason Kulatunga 2023-10-02 13:42:41 -07:00
parent af2344ec00
commit 01c293bf40
No known key found for this signature in database
5 changed files with 257 additions and 68 deletions

View File

@ -18,18 +18,19 @@ import (
type SearchParameterType string
const (
SearchParameterTypeNumber SearchParameterType = "number"
SearchParameterTypeDate SearchParameterType = "date"
//simple types
SearchParameterTypeNumber SearchParameterType = "number"
SearchParameterTypeDate SearchParameterType = "date"
SearchParameterTypeUri SearchParameterType = "uri"
SearchParameterTypeKeyword SearchParameterType = "keyword" //this is a literal/string primitive.
//complex types
SearchParameterTypeString SearchParameterType = "string"
SearchParameterTypeToken SearchParameterType = "token"
SearchParameterTypeReference SearchParameterType = "reference"
SearchParameterTypeUri SearchParameterType = "uri"
SearchParameterTypeQuantity SearchParameterType = "quantity"
SearchParameterTypeComposite SearchParameterType = "composite"
SearchParameterTypeSpecial SearchParameterType = "special"
SearchParameterTypeKeyword SearchParameterType = "keyword" //this is a literal/string primitive.
)
const TABLE_ALIAS = "fhir"
@ -58,7 +59,7 @@ func (sr *SqliteRepository) QueryResources(ctx context.Context, query models.Que
return nil, err
}
if query.Aggregations != nil && (len(query.Aggregations.GroupBy) > 0 || len(query.Aggregations.CountBy) > 0) {
if query.Aggregations != nil && (query.Aggregations.GroupBy != nil || query.Aggregations.CountBy != nil) {
results := []map[string]interface{}{}
clientResp := sqlQuery.Find(&results)
return results, clientResp.Error
@ -148,27 +149,30 @@ func (sr *SqliteRepository) sqlQueryResources(ctx context.Context, query models.
//Handle Aggregations
if len(query.Aggregations.CountBy) > 0 {
if query.Aggregations.CountBy != nil {
//populate the group by and order by clause with the count by values
query.Aggregations.OrderBy = "count(*) DESC"
query.Aggregations.OrderBy = &models.QueryResourceAggregation{
Field: "*",
Function: "count",
}
query.Aggregations.GroupBy = query.Aggregations.CountBy
if query.Aggregations.GroupBy == "*" {
if query.Aggregations.GroupBy.Field == "*" {
//we need to get the count of all resources, so we need to remove the group by clause and replace it by
// `source_resource_type` which will be the same for all resources
query.Aggregations.GroupBy = "source_resource_type"
query.Aggregations.GroupBy.Field = "source_resource_type"
}
}
//process order by clause
if len(query.Aggregations.OrderBy) > 0 {
if query.Aggregations.OrderBy != nil {
orderAsc := true //default to ascending, switch to desc if parameter is a date type.
if !strings.HasPrefix(query.Aggregations.OrderBy, "count(*)") {
orderAggregationParam, err := ProcessAggregationParameter(query.Aggregations.OrderBy, searchCodeToTypeLookup)
if !(query.Aggregations.OrderBy.Field == "*") {
orderAggregationParam, err := ProcessAggregationParameter(*query.Aggregations.OrderBy, searchCodeToTypeLookup)
if err != nil {
return nil, err
}
orderAggregationFromClause, err := SearchCodeToFromClause(orderAggregationParam)
orderAggregationFromClause, err := SearchCodeToFromClause(orderAggregationParam.SearchParameter)
if err != nil {
return nil, err
}
@ -186,17 +190,17 @@ func (sr *SqliteRepository) sqlQueryResources(ctx context.Context, query models.
orderClause = fmt.Sprintf("%s DESC", orderClause)
}
} else {
orderClause = query.Aggregations.OrderBy
orderClause = fmt.Sprintf("%s(%s) DESC", query.Aggregations.OrderBy.Function, query.Aggregations.OrderBy.Field)
}
}
//process group by clause
if len(query.Aggregations.GroupBy) > 0 {
groupAggregationParam, err := ProcessAggregationParameter(query.Aggregations.GroupBy, searchCodeToTypeLookup)
if query.Aggregations.GroupBy != nil {
groupAggregationParam, err := ProcessAggregationParameter(*query.Aggregations.GroupBy, searchCodeToTypeLookup)
if err != nil {
return nil, err
}
groupAggregationFromClause, err := SearchCodeToFromClause(groupAggregationParam)
groupAggregationFromClause, err := SearchCodeToFromClause(groupAggregationParam.SearchParameter)
if err != nil {
return nil, err
}
@ -205,8 +209,22 @@ func (sr *SqliteRepository) sqlQueryResources(ctx context.Context, query models.
groupClause = AggregationParameterToClause(groupAggregationParam)
selectClauses = []string{
fmt.Sprintf("%s as %s", groupClause, "label"),
"count(*) as value",
}
if query.Aggregations.OrderBy == nil || query.Aggregations.OrderBy.Field == "*" {
selectClauses = append(selectClauses, fmt.Sprintf("%s as %s", "count(*)", "value"))
orderClause = fmt.Sprintf("%s DESC", "count(*)")
} else {
//use the orderBy aggregation as the value
orderAggregationParam, err := ProcessAggregationParameter(*query.Aggregations.OrderBy, searchCodeToTypeLookup)
if err != nil {
return nil, err
}
orderSelectClause := AggregationParameterToClause(orderAggregationParam)
selectClauses = append(selectClauses, fmt.Sprintf("%s as %s", orderSelectClause, "value"))
}
}
}
@ -216,21 +234,22 @@ func (sr *SqliteRepository) sqlQueryResources(ctx context.Context, query models.
fromClauses = lo.Uniq(fromClauses)
fromClauses = lo.Compact(fromClauses)
fluentQuery := sr.GormClient.WithContext(ctx).
sqlQuery := sr.GormClient.WithContext(ctx).
Select(strings.Join(selectClauses, ", ")).
Where(strings.Join(whereClauses, " AND "), whereNamedParameters).
Group(groupClause).
Order(orderClause)
Order(orderClause).
Table(strings.Join(fromClauses, ", "))
//add limit and offset clauses if present
if query.Limit != nil {
fluentQuery = fluentQuery.Limit(*query.Limit)
sqlQuery = sqlQuery.Limit(*query.Limit)
}
if query.Offset != nil {
fluentQuery = fluentQuery.Offset(*query.Offset)
sqlQuery = sqlQuery.Offset(*query.Offset)
}
return fluentQuery.Table(strings.Join(fromClauses, ", ")), nil
return sqlQuery, nil
}
/// INTERNAL functionality. These functions are exported for testing, but are not available in the Interface
@ -242,6 +261,11 @@ type SearchParameter struct {
Modifier string
}
type AggregationParameter struct {
SearchParameter
Function string //count, sum, avg, min, max, etc
}
// Lists in the SearchParameterValueOperatorTree are AND'd together, and items within each SearchParameterValueOperatorTree list are OR'd together
// For example, the following would be AND'd together, and then OR'd with the next SearchParameterValueOperatorTree
//
@ -584,14 +608,31 @@ func SearchCodeToFromClause(searchParam SearchParameter) (string, error) {
return "", nil
}
func AggregationParameterToClause(aggParameter SearchParameter) string {
func AggregationParameterToClause(aggParameter AggregationParameter) string {
var clause string
switch aggParameter.Type {
case SearchParameterTypeQuantity, SearchParameterTypeToken, SearchParameterTypeString:
case SearchParameterTypeQuantity, SearchParameterTypeString:
//setup the clause
return fmt.Sprintf("(%sJson.value ->> '$.%s')", aggParameter.Name, aggParameter.Modifier)
clause = fmt.Sprintf("(%sJson.value ->> '$.%s')", aggParameter.Name, aggParameter.Modifier)
case SearchParameterTypeToken:
//modifier is optional for token types.
if aggParameter.Modifier != "" {
clause = fmt.Sprintf("(%sJson.value ->> '$.%s')", aggParameter.Name, aggParameter.Modifier)
} else {
//if no modifier is specified, use the system and code to generate the clause
//((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code'))
clause = fmt.Sprintf("((%sJson.value ->> '$.system') || '|' || (%sJson.value ->> '$.code'))", aggParameter.Name, aggParameter.Name)
}
default:
return fmt.Sprintf("%s.%s", TABLE_ALIAS, aggParameter.Name)
clause = fmt.Sprintf("%s.%s", TABLE_ALIAS, aggParameter.Name)
}
if len(aggParameter.Function) > 0 {
clause = fmt.Sprintf("%s(%s)", aggParameter.Function, clause)
}
return clause
}
// ProcessAggregationParameter processes the aggregation parameters which are fields with optional properties:
@ -602,12 +643,15 @@ func AggregationParameterToClause(aggParameter SearchParameter) string {
// eg. `identifier:code`
//
// if the a property is specified, its set as the modifier, and used when generating the SQL query groupBy, orderBy, etc clause
func ProcessAggregationParameter(aggregationFieldWithProperty string, searchParamTypeLookup map[string]string) (SearchParameter, error) {
aggregationParameter := SearchParameter{}
func ProcessAggregationParameter(aggregationFieldWithFn models.QueryResourceAggregation, searchParamTypeLookup map[string]string) (AggregationParameter, error) {
aggregationParameter := AggregationParameter{
SearchParameter: SearchParameter{},
Function: aggregationFieldWithFn.Function,
}
//determine the searchCode searchCodeModifier
//TODO: this is only applicable to string, token, reference and uri type (however unknown names & modifiers are ignored)
if aggregationFieldParts := strings.SplitN(aggregationFieldWithProperty, ":", 2); len(aggregationFieldParts) == 2 {
if aggregationFieldParts := strings.SplitN(aggregationFieldWithFn.Field, ":", 2); len(aggregationFieldParts) == 2 {
aggregationParameter.Name = aggregationFieldParts[0]
aggregationParameter.Modifier = aggregationFieldParts[1]
} else {
@ -618,7 +662,7 @@ func ProcessAggregationParameter(aggregationFieldWithProperty string, searchPara
//next, determine the searchCodeType for this Resource (or throw an error if it is unknown)
searchParamTypeStr, searchParamTypeOk := searchParamTypeLookup[aggregationParameter.Name]
if !searchParamTypeOk {
return aggregationParameter, fmt.Errorf("unknown search parameter: %s", aggregationParameter.Name)
return aggregationParameter, fmt.Errorf("unknown search parameter in aggregation: %s", aggregationParameter.Name)
} else {
aggregationParameter.Type = SearchParameterType(searchParamTypeStr)
}
@ -628,6 +672,8 @@ func ProcessAggregationParameter(aggregationFieldWithProperty string, searchPara
if len(aggregationParameter.Modifier) > 0 {
return aggregationParameter, fmt.Errorf("primitive aggregation parameter %s cannot have a property (%s)", aggregationParameter.Name, aggregationParameter.Modifier)
}
} else if aggregationParameter.Type == SearchParameterTypeToken {
//modifier is optional for token types
} else {
//complex types must have a modifier
if len(aggregationParameter.Modifier) == 0 {

View File

@ -98,7 +98,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL() {
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date ASC",
"ORDER BY fhir.sort_date DESC",
}, " "),
sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
@ -136,7 +136,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithMultipleWhereCon
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson, json_each(fhir.category) as categoryJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND ((categoryJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date ASC",
"ORDER BY fhir.sort_date DESC",
}, " "),
sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
@ -158,7 +158,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderBy
"activityCode": "test_code",
},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{OrderBy: "instantiatesUri"},
Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -193,7 +193,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordOrderByAg
Select: []string{},
Where: map[string]interface{}{},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{OrderBy: "id"},
Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "id"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -230,7 +230,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexOrderByAg
"code": "test_code",
},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{OrderBy: "valueString:value"},
Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "valueString:value"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -267,7 +267,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveCountBy
"activityCode": "test_code",
},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{CountBy: "instantiatesUri"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -304,7 +304,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordCountByAg
"activityCode": "test_code",
},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{CountBy: "source_resource_type"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "source_resource_type"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -339,7 +339,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithWildcardCountByA
Select: []string{},
Where: map[string]interface{}{},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{CountBy: "*"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "*"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -376,7 +376,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAg
"code": "test_code",
},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{CountBy: "code:code"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "code:code"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
@ -398,3 +398,120 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAg
"test_code", "00000000-0000-0000-0000-000000000000",
})
}
func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexGroupByWithOrderByMaxFnAggregation() {
//setup
sqliteRepo := suite.TestRepository.(*SqliteRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})
//test
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")
sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{
"code": "test_code",
},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{
GroupBy: &models.QueryResourceAggregation{Field: "code:code"},
OrderBy: &models.QueryResourceAggregation{Field: "sort_date", Function: "max"},
},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
statement := sqlQuery.Find(&results).Statement
sqlString := statement.SQL.String()
sqlParams := statement.Vars
//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(),
strings.Join([]string{
"SELECT (codeJson.value ->> '$.code') as label, max(fhir.sort_date) as value",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY (codeJson.value ->> '$.code')",
"ORDER BY max(fhir.sort_date) DESC",
}, " "), sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"test_code", "00000000-0000-0000-0000-000000000000",
})
}
func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifier() {
//setup
sqliteRepo := suite.TestRepository.(*SqliteRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})
//test
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")
sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{
GroupBy: &models.QueryResourceAggregation{Field: "code"},
},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
statement := sqlQuery.Find(&results).Statement
sqlString := statement.SQL.String()
sqlParams := statement.Vars
//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(),
strings.Join([]string{
"SELECT ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code')) as label, count(*) as value",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE (user_id = ?)",
"GROUP BY ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code'))",
"ORDER BY count(*) DESC",
}, " "), sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"00000000-0000-0000-0000-000000000000",
})
}
func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifierWithLimit() {
//setup
sqliteRepo := suite.TestRepository.(*SqliteRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})
//test
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")
limit := 10
sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{},
From: "Observation",
Limit: &limit,
Aggregations: &models.QueryResourceAggregations{
GroupBy: &models.QueryResourceAggregation{Field: "code"},
},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
statement := sqlQuery.Find(&results).Statement
sqlString := statement.SQL.String()
sqlParams := statement.Vars
//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(),
strings.Join([]string{
"SELECT ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code')) as label, count(*) as value",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE (user_id = ?)",
"GROUP BY ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code'))",
"ORDER BY count(*) DESC",
"LIMIT 10",
}, " "), sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"00000000-0000-0000-0000-000000000000",
})
}

View File

@ -19,10 +19,15 @@ type QueryResource struct {
}
type QueryResourceAggregations struct {
CountBy string `json:"count_by"` //alias for both groupby and orderby, cannot be used together
CountBy *QueryResourceAggregation `json:"count_by,omitempty"` //alias for both groupby and orderby, cannot be used together
GroupBy string `json:"group_by"`
OrderBy string `json:"order_by"`
GroupBy *QueryResourceAggregation `json:"group_by,omitempty"`
OrderBy *QueryResourceAggregation `json:"order_by,omitempty"`
}
type QueryResourceAggregation struct {
Field string `json:"field"`
Function string `json:"fn"`
}
func (q *QueryResource) Validate() error {
@ -38,26 +43,44 @@ func (q *QueryResource) Validate() error {
if len(q.Select) > 0 {
return fmt.Errorf("cannot use 'select' and 'aggregations' together")
}
if len(q.Aggregations.CountBy) > 0 {
if len(q.Aggregations.GroupBy) > 0 {
if q.Aggregations.CountBy != nil {
if len(q.Aggregations.CountBy.Field) == 0 {
return fmt.Errorf("if 'count_by' is present, field must be populated")
}
if strings.Contains(q.Aggregations.CountBy.Field, " ") {
return fmt.Errorf("count_by cannot have spaces (or aliases)")
}
}
if q.Aggregations.GroupBy != nil {
if len(q.Aggregations.GroupBy.Field) == 0 {
return fmt.Errorf("if 'group_by' is present, field must be populated")
}
if strings.Contains(q.Aggregations.GroupBy.Field, " ") {
return fmt.Errorf("group_by cannot have spaces (or aliases)")
}
}
if q.Aggregations.OrderBy != nil {
if len(q.Aggregations.OrderBy.Field) == 0 {
return fmt.Errorf("if 'order_by' is present, field must be populated")
}
if strings.Contains(q.Aggregations.OrderBy.Field, " ") {
return fmt.Errorf("order_by cannot have spaces (or aliases)")
}
}
if q.Aggregations.CountBy != nil {
if q.Aggregations.GroupBy != nil {
return fmt.Errorf("cannot use 'count_by' and 'group_by' together")
}
if len(q.Aggregations.OrderBy) > 0 {
if q.Aggregations.OrderBy != nil {
return fmt.Errorf("cannot use 'count_by' and 'order_by' together")
}
}
if len(q.Aggregations.CountBy) == 0 && len(q.Aggregations.OrderBy) == 0 && len(q.Aggregations.GroupBy) == 0 {
if q.Aggregations.CountBy == nil && q.Aggregations.OrderBy == nil && q.Aggregations.GroupBy == nil {
return fmt.Errorf("aggregations must have at least one of 'count_by', 'group_by', or 'order_by'")
}
if strings.Contains(q.Aggregations.CountBy, " ") {
return fmt.Errorf("count_by cannot have spaces (or aliases)")
}
if strings.Contains(q.Aggregations.GroupBy, " ") {
return fmt.Errorf("group_by cannot have spaces (or aliases)")
}
if strings.Contains(q.Aggregations.OrderBy, " ") {
return fmt.Errorf("order_by cannot have spaces (or aliases)")
}
}
if q.Limit != nil && *q.Limit < 0 {

View File

@ -13,15 +13,18 @@ func TestQueryResource_Validate(t *testing.T) {
}{
{QueryResource{Use: "test"}, "'use' is not supported yet", true},
{QueryResource{}, "'from' is required", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: "test"}}, "", false},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: &QueryResourceAggregation{Field: ""}}}, "if 'count_by' is present, field must be populated", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{GroupBy: &QueryResourceAggregation{Field: ""}}}, "if 'group_by' is present, field must be populated", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{OrderBy: &QueryResourceAggregation{Field: ""}}}, "if 'order_by' is present, field must be populated", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: &QueryResourceAggregation{Field: "test"}}}, "", false},
{QueryResource{Select: []string{"test"}, From: "test", Aggregations: &QueryResourceAggregations{}}, "cannot use 'select' and 'aggregations' together", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: "test", GroupBy: "test"}}, "cannot use 'count_by' and 'group_by' together", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: "test", OrderBy: "test"}}, "cannot use 'count_by' and 'order_by' together", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: &QueryResourceAggregation{Field: "test"}, GroupBy: &QueryResourceAggregation{Field: "test"}}}, "cannot use 'count_by' and 'group_by' together", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: &QueryResourceAggregation{Field: "test"}, OrderBy: &QueryResourceAggregation{Field: "test"}}}, "cannot use 'count_by' and 'order_by' together", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{}}, "aggregations must have at least one of 'count_by', 'group_by', or 'order_by'", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: "test:property"}}, "", false},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: "test:property as HELLO"}}, "count_by cannot have spaces (or aliases)", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{GroupBy: "test:property as HELLO"}}, "group_by cannot have spaces (or aliases)", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{OrderBy: "test:property as HELLO"}}, "order_by cannot have spaces (or aliases)", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: &QueryResourceAggregation{Field: "test:property"}}}, "", false},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{CountBy: &QueryResourceAggregation{Field: "test:property as HELLO"}}}, "count_by cannot have spaces (or aliases)", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{GroupBy: &QueryResourceAggregation{Field: "test:property as HELLO"}}}, "group_by cannot have spaces (or aliases)", true},
{QueryResource{From: "test", Aggregations: &QueryResourceAggregations{OrderBy: &QueryResourceAggregation{Field: "test:property as HELLO"}}}, "order_by cannot have spaces (or aliases)", true},
}
//test && assert

View File

@ -132,7 +132,7 @@
"from": "Observation",
"where": {},
"aggregations":{
"count_by": "code:code"
"count_by": {"field": "code:code" }
}
}
}],
@ -158,7 +158,7 @@
"from": "Immunization",
"where": {},
"aggregations":{
"count_by": "*"
"count_by": {"field": "*" }
}
}
},
@ -171,7 +171,7 @@
"from": "Claim",
"where": {},
"aggregations":{
"count_by": "*"
"count_by": {"field": "*" }
}
}
}],