diff --git a/backend/pkg/database/gorm_repository_query.go b/backend/pkg/database/gorm_repository_query.go index 562cb4a0..f3284f85 100644 --- a/backend/pkg/database/gorm_repository_query.go +++ b/backend/pkg/database/gorm_repository_query.go @@ -3,7 +3,6 @@ package database import ( "context" "fmt" - "log" "sort" "strconv" "strings" @@ -231,14 +230,9 @@ func (gr *GormRepository) sqlQueryResources(ctx context.Context, query models.Qu } } - log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log.Printf("whereClauses: %v", whereClauses) - log.Printf("whereNamedParameters: %v", whereNamedParameters) + // Debugging + //log.Printf("whereClauses: %v", whereClauses) + //log.Printf("whereNamedParameters: %v", whereNamedParameters) //ensure Where and From clauses are unique whereClauses = lo.Uniq(whereClauses) @@ -472,8 +466,12 @@ func ProcessSearchParameterValue(searchParameter SearchParameter, searchValueWit return searchParameterValue, nil } -func NamedParameterWithSuffix(parameterName string, suffix string) string { - return fmt.Sprintf("%s_%s", parameterName, suffix) +func NamedParameterWithSuffix(parameterName string, parameterModifier string, suffix string) string { + if len(parameterModifier) > 0 { + return fmt.Sprintf("%s-%s_%s", parameterName, parameterModifier, suffix) + } else { + return fmt.Sprintf("%s_%s", parameterName, suffix) + } } // SearchCodeToWhereClause converts a searchCode and searchCodeValue to a where clause and a map of named parameters @@ -481,10 +479,10 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc //add named parameters to the lookup map. Basically, this is a map of all the named parameters that will be used in the where clause we're generating searchClauseNamedParams := map[string]interface{}{ - NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix): searchParamValue.Value, + NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix): searchParamValue.Value, } for k, v := range searchParamValue.SecondaryValues { - searchClauseNamedParams[NamedParameterWithSuffix(k, namedParameterSuffix)] = v + searchClauseNamedParams[NamedParameterWithSuffix(k, "", namedParameterSuffix)] = v } //parse the searchCode and searchCodeValue to determine the correct where clause @@ -495,27 +493,27 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc case SearchParameterTypeNumber, SearchParameterTypeDate: if searchParamValue.Prefix == "" || searchParamValue.Prefix == "eq" { - return fmt.Sprintf("(%s = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParamValue.Prefix == "lt" || searchParamValue.Prefix == "eb" { - return fmt.Sprintf("(%s < @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s < @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParamValue.Prefix == "le" { - return fmt.Sprintf("(%s <= @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s <= @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParamValue.Prefix == "gt" || searchParamValue.Prefix == "sa" { - return fmt.Sprintf("(%s > @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s > @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParamValue.Prefix == "ge" { - return fmt.Sprintf("(%s >= @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s >= @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParamValue.Prefix == "ne" { - return fmt.Sprintf("(%s <> @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s <> @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParam.Modifier == "ap" { return "", nil, fmt.Errorf("search modifier 'ap' not supported for search parameter type %s (%s=%s)", searchParam.Type, searchParam.Name, searchParamValue.Value) } case SearchParameterTypeUri: if searchParam.Modifier == "" { - return fmt.Sprintf("(%s = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParam.Modifier == "below" { - searchClauseNamedParams[NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)] = searchParamValue.Value.(string) + "%" // column starts with "http://example.com" - return fmt.Sprintf("(%s LIKE @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + searchClauseNamedParams[NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)] = searchParamValue.Value.(string) + "%" // column starts with "http://example.com" + return fmt.Sprintf("(%s LIKE @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParam.Modifier == "above" { return "", nil, fmt.Errorf("search modifier 'above' not supported for search parameter type %s (%s=%s)", searchParam.Type, searchParam.Name, searchParamValue.Value) } @@ -524,14 +522,14 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// case SearchParameterTypeString: if searchParam.Modifier == "" { - searchClauseNamedParams[NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)] = searchParamValue.Value.(string) + "%" // "eve" matches "Eve" and "Evelyn" - return fmt.Sprintf("(%sJson.value LIKE @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + searchClauseNamedParams[NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)] = searchParamValue.Value.(string) + "%" // "eve" matches "Eve" and "Evelyn" + return fmt.Sprintf("(%sJson.value LIKE @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParam.Modifier == "exact" { // "eve" matches "eve" (not "Eve" or "EVE") - return fmt.Sprintf("(%sJson.value = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%sJson.value = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } else if searchParam.Modifier == "contains" { - searchClauseNamedParams[NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)] = "%" + searchParamValue.Value.(string) + "%" // "eve" matches "Eve", "Evelyn" and "Severine" - return fmt.Sprintf("(%sJson.value LIKE @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + searchClauseNamedParams[NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)] = "%" + searchParamValue.Value.(string) + "%" // "eve" matches "Eve", "Evelyn" and "Severine" + return fmt.Sprintf("(%sJson.value LIKE @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil } case SearchParameterTypeQuantity: @@ -539,17 +537,17 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc var clause string if searchParamValue.Prefix == "" || searchParamValue.Prefix == "eq" { //TODO: when no prefix is specified, we need to search using BETWEEN (+/- 0.05) - clause = fmt.Sprintf("%sJson.value ->> '$.value' = @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)) + clause = fmt.Sprintf("%sJson.value ->> '$.value' = @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)) } else if searchParamValue.Prefix == "lt" || searchParamValue.Prefix == "eb" { - clause = fmt.Sprintf("%sJson.value ->> '$.value' < @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)) + clause = fmt.Sprintf("%sJson.value ->> '$.value' < @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)) } else if searchParamValue.Prefix == "le" { - clause = fmt.Sprintf("%sJson.value ->> '$.value' <= @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)) + clause = fmt.Sprintf("%sJson.value ->> '$.value' <= @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)) } else if searchParamValue.Prefix == "gt" || searchParamValue.Prefix == "sa" { - clause = fmt.Sprintf("%sJson.value ->> '$.value' > @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)) + clause = fmt.Sprintf("%sJson.value ->> '$.value' > @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)) } else if searchParamValue.Prefix == "ge" { - clause = fmt.Sprintf("%sJson.value ->> '$.value' >= @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)) + clause = fmt.Sprintf("%sJson.value ->> '$.value' >= @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)) } else if searchParamValue.Prefix == "ne" { - clause = fmt.Sprintf("%sJson.value ->> '$.value' <> @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)) + clause = fmt.Sprintf("%sJson.value ->> '$.value' <> @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)) } else if searchParamValue.Prefix == "ap" { return "", nil, fmt.Errorf("search modifier 'ap' not supported for search parameter type %s (%s=%s)", searchParam.Type, searchParam.Name, searchParamValue.Value) } @@ -561,7 +559,7 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc for _, k := range allowedSecondaryKeys { namedParameterKey := fmt.Sprintf("%s%s", searchParam.Name, strings.Title(k)) if _, ok := searchParamValue.SecondaryValues[namedParameterKey]; ok { - clause += fmt.Sprintf(` AND %sJson.value ->> '$.%s' = @%s`, searchParam.Name, k, NamedParameterWithSuffix(namedParameterKey, namedParameterSuffix)) + clause += fmt.Sprintf(` AND %sJson.value ->> '$.%s' = @%s`, searchParam.Name, k, NamedParameterWithSuffix(namedParameterKey, searchParam.Modifier, namedParameterSuffix)) } } @@ -591,9 +589,9 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc clause := []string{} if searchParamValue.Value.(string) != "" { if searchParam.Modifier == "" { - clause = append(clause, fmt.Sprintf("%sJson.value ->> '$.code' = @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix))) + clause = append(clause, fmt.Sprintf("%sJson.value ->> '$.code' = @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix))) } else if searchParam.Modifier == "not" { - clause = append(clause, fmt.Sprintf("%sJson.value ->> '$.code' <> @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix))) + clause = append(clause, fmt.Sprintf("%sJson.value ->> '$.code' <> @%s", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix))) } } @@ -604,14 +602,14 @@ func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue Searc for _, k := range allowedSecondaryKeys { namedParameterKey := fmt.Sprintf("%s%s", searchParam.Name, strings.Title(k)) if _, ok := searchParamValue.SecondaryValues[namedParameterKey]; ok { - clause = append(clause, fmt.Sprintf(`%sJson.value ->> '$.%s' = @%s`, searchParam.Name, k, NamedParameterWithSuffix(namedParameterKey, namedParameterSuffix))) + clause = append(clause, fmt.Sprintf(`%sJson.value ->> '$.%s' = @%s`, searchParam.Name, k, NamedParameterWithSuffix(namedParameterKey, searchParam.Modifier, namedParameterSuffix))) } } return fmt.Sprintf("(%s)", strings.Join(clause, " AND ")), searchClauseNamedParams, nil case SearchParameterTypeKeyword: //setup the clause - return fmt.Sprintf("(%s = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, namedParameterSuffix)), searchClauseNamedParams, nil + return fmt.Sprintf("(%s = @%s)", searchParam.Name, NamedParameterWithSuffix(searchParam.Name, searchParam.Modifier, namedParameterSuffix)), searchClauseNamedParams, nil case SearchParameterTypeReference: return "", nil, fmt.Errorf("search parameter type %s not supported", searchParam.Type) } diff --git a/backend/pkg/database/gorm_repository_query_sql_test.go b/backend/pkg/database/gorm_repository_query_sql_test.go index 3605574a..756b8569 100644 --- a/backend/pkg/database/gorm_repository_query_sql_test.go +++ b/backend/pkg/database/gorm_repository_query_sql_test.go @@ -149,7 +149,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithMultipleWhereCon }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenWithNotModifier() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -160,9 +160,10 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenWithNotModi sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, Where: map[string]interface{}{ - "code:not": "test_code", + "activityCode": "test_code", }, - From: "Observation", + From: "CarePlan", + Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -175,18 +176,17 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenWithNotModi require.Equal(suite.T(), strings.Join([]string{ "SELECT fhir.*", - "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson", - "WHERE ((codeJson.value ->> '$.code' <> ?)) AND (user_id = ?)", + "FROM fhir_care_plan as fhir, json_each(fhir.activityCode) as activityCodeJson", + "WHERE ((activityCodeJson.value ->> '$.code' = ?)) AND (user_id = ?)", "GROUP BY `fhir`.`id`", - "ORDER BY fhir.sort_date DESC", - }, " "), - sqlString) + "ORDER BY fhir.instantiatesUri ASC", + }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ "test_code", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleANDValuesWithNotModifier() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordOrderByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -195,11 +195,10 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleAND authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username") sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ - Select: []string{}, - Where: map[string]interface{}{ - "code:not": []string{"test_code", "test_code2"}, //AND condition - }, - From: "Observation", + Select: []string{}, + Where: map[string]interface{}{}, + From: "CarePlan", + Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "id"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -212,18 +211,17 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleAND require.Equal(suite.T(), strings.Join([]string{ "SELECT fhir.*", - "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson", - "WHERE ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?)) AND (user_id = ?)", + "FROM fhir_care_plan as fhir", + "WHERE (user_id = ?)", "GROUP BY `fhir`.`id`", - "ORDER BY fhir.sort_date DESC", - }, " "), - sqlString) + "ORDER BY fhir.id ASC", + }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ - "test_code", "test_code2", "00000000-0000-0000-0000-000000000000", + "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleORValues() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexOrderByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -234,9 +232,10 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleORV sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, Where: map[string]interface{}{ - "code": "test_code,test_code2", //OR condition + "code": "test_code", }, - From: "Observation", + From: "Observation", + Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "valueString:value"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -249,18 +248,17 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleORV require.Equal(suite.T(), strings.Join([]string{ "SELECT fhir.*", - "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson", - "WHERE ((codeJson.value ->> '$.code' = ?)) OR ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", + "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson, json_each(fhir.valueString) as valueStringJson", + "WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", "GROUP BY `fhir`.`id`", - "ORDER BY fhir.sort_date DESC", - }, " "), - sqlString) + "ORDER BY (valueStringJson.value ->> '$.value') ASC", + }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ - "test_code", "test_code2", "00000000-0000-0000-0000-000000000000", + "test_code", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMixedMultipleANDORValues() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveCountByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -271,10 +269,10 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMixedMultip sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, Where: map[string]interface{}{ - "code:not": []string{"test_code", "test_code2", "test_code3"}, //AND condition - "code": "test_code4,test_code5,test_code6", //OR condition + "activityCode": "test_code", }, - From: "Observation", + From: "CarePlan", + Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -286,19 +284,18 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMixedMultip require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT fhir.*", - "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson", - "WHERE ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", - "GROUP BY `fhir`.`id`", - "ORDER BY fhir.sort_date DESC", - }, " "), - sqlString) + "SELECT count(*) as value, fhir.instantiatesUri as label", + "FROM fhir_care_plan as fhir, json_each(fhir.activityCode) as activityCodeJson", + "WHERE ((activityCodeJson.value ->> '$.code' = ?)) AND (user_id = ?)", + "GROUP BY `fhir`.`instantiatesUri`", + "ORDER BY count(*) DESC", + }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ - "test_code", "test_code2", "test_code3", "test_code4", "00000000-0000-0000-0000-000000000000", + "test_code", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordCountByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -312,7 +309,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderBy "activityCode": "test_code", }, From: "CarePlan", - Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}}, + Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "source_resource_type"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -324,18 +321,18 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderBy require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT fhir.*", + "SELECT count(*) as value, fhir.source_resource_type as label", "FROM fhir_care_plan as fhir, json_each(fhir.activityCode) as activityCodeJson", "WHERE ((activityCodeJson.value ->> '$.code' = ?)) AND (user_id = ?)", - "GROUP BY `fhir`.`id`", - "ORDER BY fhir.instantiatesUri ASC", + "GROUP BY `fhir`.`source_resource_type`", + "ORDER BY count(*) DESC", }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ "test_code", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordOrderByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithWildcardCountByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -347,7 +344,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordOrderByAg Select: []string{}, Where: map[string]interface{}{}, From: "CarePlan", - Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "id"}}, + Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "*"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -359,18 +356,18 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordOrderByAg require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT fhir.*", + "SELECT count(*) as value, fhir.source_resource_type as label", "FROM fhir_care_plan as fhir", "WHERE (user_id = ?)", - "GROUP BY `fhir`.`id`", - "ORDER BY fhir.id ASC", + "GROUP BY `fhir`.`source_resource_type`", + "ORDER BY count(*) DESC", }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexOrderByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -384,7 +381,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexOrderByAg "code": "test_code", }, From: "Observation", - Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "valueString:value"}}, + Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "code:code"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -396,18 +393,18 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexOrderByAg require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT fhir.*", - "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson, json_each(fhir.valueString) as valueStringJson", + "SELECT (codeJson.value ->> '$.code') as label, count(*) as value", + "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson", "WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", - "GROUP BY `fhir`.`id`", - "ORDER BY (valueStringJson.value ->> '$.value') ASC", + "GROUP BY (codeJson.value ->> '$.code')", + "ORDER BY count(*) DESC", }, " "), sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ "test_code", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveCountByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexGroupByWithOrderByMaxFnAggregation() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -418,10 +415,13 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveCountBy sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, Where: map[string]interface{}{ - "activityCode": "test_code", + "code": "test_code", + }, + From: "Observation", + Aggregations: &models.QueryResourceAggregations{ + GroupBy: &models.QueryResourceAggregation{Field: "code:code"}, + OrderBy: &models.QueryResourceAggregation{Field: "sort_date", Function: "max"}, }, - From: "CarePlan", - Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -433,18 +433,18 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveCountBy require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT count(*) as value, fhir.instantiatesUri as label", - "FROM fhir_care_plan as fhir, json_each(fhir.activityCode) as activityCodeJson", - "WHERE ((activityCodeJson.value ->> '$.code' = ?)) AND (user_id = ?)", - "GROUP BY `fhir`.`instantiatesUri`", - "ORDER BY count(*) DESC", + "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_WithKeywordCountByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifier() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -454,11 +454,11 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordCountByAg sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, - Where: map[string]interface{}{ - "activityCode": "test_code", + Where: map[string]interface{}{}, + From: "Observation", + Aggregations: &models.QueryResourceAggregations{ + GroupBy: &models.QueryResourceAggregation{Field: "code"}, }, - From: "CarePlan", - Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "source_resource_type"}}, }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -470,18 +470,18 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordCountByAg require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT count(*) as value, fhir.source_resource_type as label", - "FROM fhir_care_plan as fhir, json_each(fhir.activityCode) as activityCodeJson", - "WHERE ((activityCodeJson.value ->> '$.code' = ?)) AND (user_id = ?)", - "GROUP BY `fhir`.`source_resource_type`", + "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{}{ - "test_code", "00000000-0000-0000-0000-000000000000", + "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithWildcardCountByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifierWithLimit() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -489,11 +489,15 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithWildcardCountByA //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: "CarePlan", - Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "*"}}, + 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{} @@ -505,18 +509,19 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithWildcardCountByA require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT count(*) as value, fhir.source_resource_type as label", - "FROM fhir_care_plan as fhir", + "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 `fhir`.`source_resource_type`", + "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", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenWithNotModifier() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -527,10 +532,9 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAg sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, Where: map[string]interface{}{ - "code": "test_code", + "code:not": "test_code", }, - From: "Observation", - Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "code:code"}}, + From: "Observation", }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -542,18 +546,19 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAg require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT (codeJson.value ->> '$.code') as label, count(*) as value", + "SELECT fhir.*", "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 count(*) DESC", - }, " "), sqlString) + "WHERE ((codeJson.value ->> '$.code' <> ?)) AND (user_id = ?)", + "GROUP BY `fhir`.`id`", + "ORDER BY fhir.sort_date DESC", + }, " "), + sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ "test_code", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexGroupByWithOrderByMaxFnAggregation() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleANDValuesWithNotModifier() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -564,13 +569,46 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexGroupByWi sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, Where: map[string]interface{}{ - "code": "test_code", + "code:not": []string{"test_code", "test_code2"}, //AND condition }, 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 fhir.*", + "FROM fhir_observation as fhir, json_each(fhir.code) as codeJson", + "WHERE ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?)) AND (user_id = ?)", + "GROUP BY `fhir`.`id`", + "ORDER BY fhir.sort_date DESC", + }, " "), + sqlString) + require.Equal(suite.T(), sqlParams, []interface{}{ + "test_code", "test_code2", "00000000-0000-0000-0000-000000000000", + }) +} + +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleORValues() { + //setup + sqliteRepo := suite.TestRepository.(*GormRepository) + 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,test_code2", //OR condition }, + From: "Observation", }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -582,18 +620,19 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexGroupByWi require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT (codeJson.value ->> '$.code') as label, max(fhir.sort_date) as value", + "SELECT fhir.*", "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) + "WHERE ((codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", + "GROUP BY `fhir`.`id`", + "ORDER BY fhir.sort_date DESC", + }, " "), + sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ - "test_code", "00000000-0000-0000-0000-000000000000", + "test_code", "test_code2", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifier() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleORANDValues() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -603,11 +642,10 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoMo sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{ Select: []string{}, - Where: map[string]interface{}{}, - From: "Observation", - Aggregations: &models.QueryResourceAggregations{ - GroupBy: &models.QueryResourceAggregation{Field: "code"}, + Where: map[string]interface{}{ + "code": []string{"test_code,test_code2", "test_code3,test_code4"}, //OR-AND condition }, + From: "Observation", }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -619,18 +657,19 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoMo require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code')) as label, count(*) as value", + "SELECT fhir.*", "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) + "WHERE ((codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?)) AND ((codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", + "GROUP BY `fhir`.`id`", + "ORDER BY fhir.sort_date DESC", + }, " "), + sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ - "00000000-0000-0000-0000-000000000000", + "test_code", "test_code2", "test_code3", "test_code4", "00000000-0000-0000-0000-000000000000", }) } -func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifierWithLimit() { +func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleModifiersMultipleANDORValues() { //setup sqliteRepo := suite.TestRepository.(*GormRepository) sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true}) @@ -638,15 +677,13 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoMo //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"}, + Where: map[string]interface{}{ + "code:not": []string{"test_code", "test_code2,test_code3"}, //AND-OR condition + "code": "test_code4,test_code5,test_code6", //OR condition }, + From: "Observation", }) require.NoError(suite.T(), err) var results []map[string]interface{} @@ -658,14 +695,14 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoMo require.NoError(suite.T(), err) require.Equal(suite.T(), strings.Join([]string{ - "SELECT ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code')) as label, count(*) as value", + "SELECT fhir.*", "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) + "WHERE ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?) OR (codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?)) AND (user_id = ?)", + "GROUP BY `fhir`.`id`", + "ORDER BY fhir.sort_date DESC", + }, " "), + sqlString) require.Equal(suite.T(), sqlParams, []interface{}{ - "00000000-0000-0000-0000-000000000000", + "test_code", "test_code2", "test_code3", "test_code4", "test_code5", "test_code6", "00000000-0000-0000-0000-000000000000", }) }