From d9bb9f612fe42177f22be746625cb318f7c402da Mon Sep 17 00:00:00 2001 From: "Wang, Yimo" Date: Fri, 19 Sep 2025 09:57:14 -0400 Subject: [PATCH] allow services and hosted services to take a list of enums as a param --- .../ParameterValidationContextExecutor.java | 2 +- .../executionPlan_generation.pure | 1 - .../relational/freemarker/TestFreemarker.java | 82 +++++++++++++++++++ .../functions/in/TestPlanExecutionForIn.java | 66 +++++++++++++++ .../extension/extension_relational.pure | 2 +- .../relationalMappingExecution.pure | 23 +++++- .../service/TestServiceTestSuite.java | 13 +++ ...lational-service-enum-list-parameters.pure | 75 +++++++++++++++++ 8 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 legend-engine-xts-service/legend-engine-test-runner-service/src/test/resources/testable/relational/legend-testable-relational-service-enum-list-parameters.pure diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-executionPlan-execution/legend-engine-executionPlan-execution/src/main/java/org/finos/legend/engine/plan/execution/validation/ParameterValidationContextExecutor.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-executionPlan-execution/legend-engine-executionPlan-execution/src/main/java/org/finos/legend/engine/plan/execution/validation/ParameterValidationContextExecutor.java index 5519ef88ad7..1afb748355b 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-executionPlan-execution/legend-engine-executionPlan-execution/src/main/java/org/finos/legend/engine/plan/execution/validation/ParameterValidationContextExecutor.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-executionPlan-execution/legend-engine-executionPlan-execution/src/main/java/org/finos/legend/engine/plan/execution/validation/ParameterValidationContextExecutor.java @@ -39,7 +39,7 @@ public ValidationResult visit(EnumValidationContext enumValidationContext) List validEnumValues = enumValidationContext.validEnumValues; if (value instanceof List) { - throw new IllegalArgumentException("Collection of Enums (" + value + ") is not supported as service parameter"); + return ((List) value).stream().allMatch(item -> validEnumValues.contains(item.toString())) ? ValidationResult.successValidationResult() : ValidationResult.errorValidationResult("Invalid enum value " + value + " for " + ((PackageableType) var.genericType.rawType).fullPath + ", valid enum values: " + validEnumValues); } return (validEnumValues.contains(value.toString())) ? ValidationResult.successValidationResult() : ValidationResult.errorValidationResult("Invalid enum value " + value + " for " + ((PackageableType) var.genericType.rawType).fullPath + ", valid enum values: " + validEnumValues); } diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/executionPlan/executionPlan_generation.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/executionPlan/executionPlan_generation.pure index 53dcf8c8e7f..0b9e5bb060a 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/executionPlan/executionPlan_generation.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/executionPlan/executionPlan_generation.pure @@ -232,7 +232,6 @@ function meta::pure::executionPlan::generatePlatformCode(plan:ExecutionPlan[1], function <> meta::pure::executionPlan::addFunctionParametersValidationNode(node:ExecutionNode[1], f:FunctionDefinition[1], planVarPlaceHolders:PlanVarPlaceHolder[*], routedFunction:FunctionDefinition[1]):ExecutionNode[1] { let collectionEnumParams = $planVarPlaceHolders->filter(p | $p.genericType.rawType->toOne()->instanceOf(Enumeration) && $p.multiplicity == ZeroMany); - assert($collectionEnumParams->size() == 0, |'Collection of Enums is not supported as service parameter ' + $collectionEnumParams->map(p | $p.name)->makeString('[',', ',']')); let parameterValidationContexts = $planVarPlaceHolders->map(p | if( [ diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/freemarker/TestFreemarker.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/freemarker/TestFreemarker.java index 4448444dcb2..ff2fcb7fdd1 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/freemarker/TestFreemarker.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/freemarker/TestFreemarker.java @@ -240,6 +240,47 @@ public void testEnumPlaceHolderIfOp() throws Exception Assert.assertEquals("select case when 'EMEA' = 'EMEA' then \"root\".COUNTY else \"root\".COUNTY end as \"county\", \"root\".FIPS as \"fips\" from testTable as \"root\"", result4); } + + @Test + public void testTemplatesEnumOne() + { + String testQuery = "select \"root\".id as \"Id\", \"root\".myEnum as \"EnumCol\" from ItemTable as \"root\" where (${optionalVarPlaceHolderOperationSelector(oneEnum, equalEnumOperationSelector(enumMap_mappings_MyMapping_MyEnumMapping(oneEnum), '\"root\".myEnum in (${enumMap_mappings_MyMapping_MyEnumMapping(oneEnum)})', '\"root\".myEnum = ${enumMap_mappings_MyMapping_MyEnumMapping(oneEnum)}'), '0 = 1')})"; + String enumFullPath = "mappings_MyMapping_MyEnumMapping"; + String enumHashMap = "\"VALUE_1\":\"'VAL1A', 'VAL1B', 'VAL1C'\", \"VALUE_2\":\"'VAL2'\", \"VALUE_3\":\"'VAL3'\", \"VALUE_4\":\"'VAL_FILL'\", \"VALUE_5\":\"'VAL_FILL'\""; + + Map vars = new HashMap<>(); + vars.put("oneEnum", "VALUE_1"); + + String templates = renderCollectionWithDefaultTemplate() + collectionSizeTemplate() + optionalVarPlaceHolderOperationSelectorTemplate() + varPlaceHolderToStringTemplate() + equalEnumOperationSelector(); + String result = RelationalExecutor.process(testQuery, vars, templates + enumSupportFunction(enumFullPath, enumHashMap)); + Assert.assertEquals("select \"root\".id as \"Id\", \"root\".myEnum as \"EnumCol\" from ItemTable as \"root\" where (\"root\".myEnum in ('VAL1A', 'VAL1B', 'VAL1C'))", result.trim()); + + // Test that old enum map function (which does not handle sequences) gives the same output - backwards compatibility. + String oldEnumMapSupportFunction = "<#function enumMap_mappings_MyMapping_MyEnumMapping inputVal> <#assign enumMap = { \"VALUE_1\":\"'VAL1A', 'VAL1B', 'VAL1C'\", \"VALUE_2\":\"'VAL2'\", \"VALUE_3\":\"'VAL3'\", \"VALUE_4\":\"'VAL_FILL'\", \"VALUE_5\":\"'VAL_FILL'\" }> <#if inputVal?has_content> <#return enumMap[inputVal]> <#else> <#return \"\"> "; + String oldResult = RelationalExecutor.process(testQuery, vars, templates + oldEnumMapSupportFunction); + Assert.assertEquals(result.trim(), oldResult.trim()); + } + + @Test + public void testTemplatesEnumList() + { + String testQuery = "select \"root\".id as \"Id\", \"root\".myEnum as \"EnumCol\" from ItemTable as \"root\" where (${optionalVarPlaceHolderOperationSelector(enumList![], equalEnumOperationSelector(enumMap_mappings_MyMapping_MyEnumMapping(enumList![]), '\"root\".myEnum in (${enumMap_mappings_MyMapping_MyEnumMapping(enumList![])})', '\"root\".myEnum = ${enumMap_mappings_MyMapping_MyEnumMapping(enumList![])}'), '0 = 1')})"; + String enumFullPath = "mappings_MyMapping_MyEnumMapping"; + String enumHashMap = "\"VALUE_1\":\"'VAL1A', 'VAL1B', 'VAL1C'\", \"VALUE_2\":\"'VAL2'\", \"VALUE_3\":\"'VAL3'\", \"VALUE_4\":\"'VAL_FILL'\", \"VALUE_5\":\"'VAL_FILL'\""; + + ArrayList enumList = new ArrayList<>(); + enumList.add("VALUE_1"); + enumList.add("VALUE_2"); + enumList.add("VALUE_3"); + enumList.add("VALUE_4"); + enumList.add("VALUE_5"); + Map vars = new HashMap<>(); + vars.put("enumList", enumList); + + String result = RelationalExecutor.process(testQuery, vars, renderCollectionWithDefaultTemplate() + collectionSizeTemplate() + optionalVarPlaceHolderOperationSelectorTemplate() + varPlaceHolderToStringTemplate() + equalEnumOperationSelector() + enumSupportFunction(enumFullPath, enumHashMap)); + Assert.assertEquals("select \"root\".id as \"Id\", \"root\".myEnum as \"EnumCol\" from ItemTable as \"root\" where (\"root\".myEnum in ('VAL1A', 'VAL1B', 'VAL1C', 'VAL2', 'VAL3', 'VAL_FILL', 'VAL_FILL'))", result.trim()); + } + @Test public void testTimeZoneOffsetAndCode() { @@ -328,6 +369,47 @@ public static String collectionSizeTemplate() ""; } + public static String optionalVarPlaceHolderOperationSelectorTemplate() + { + return "<#function optionalVarPlaceHolderOperationSelector optionalParameter trueClause falseClause>" + + "<#if optionalParameter?has_content || optionalParameter?is_string>" + + "<#return trueClause>" + + "<#else>" + + "<#return falseClause>" + + ""; + } + + public static String varPlaceHolderToStringTemplate() + { + return "<#function varPlaceHolderToString optionalParameter prefix suffix replacementMap defaultValue>" + + "<#if optionalParameter?is_enumerable && !optionalParameter?has_content>" + + "<#return defaultValue>" + + "<#else>" + + "<#assign newParam = optionalParameter>" + + "<#list replacementMap as oldValue, newValue>" + + "<#assign newParam = newParam?replace(oldValue, newValue)>" + + "" + + "<#return prefix + newParam + suffix>" + + ""; + } + + public static String enumSupportFunction(String enumFullPath, String enumHashMap) + { + return "<#function enumMap_" + enumFullPath + " inputVal>" + + "<#assign enumMap = {" + enumHashMap + "}>" + + "<#if inputVal?has_content>" + + "<#if inputVal?is_sequence>" + + "<#assign results = []>" + + "<#list inputVal as item>" + + "<#assign results += [enumMap[item]!\"\"]>" + + "<#return results?join(\", \")>" + + "<#else>" + + "<#return enumMap[inputVal]!\"\">" + + "<#else>" + + "<#return \"\"> " + + " "; + } + @Test public void roleTransform() throws Exception { diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/full/functions/in/TestPlanExecutionForIn.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/full/functions/in/TestPlanExecutionForIn.java index 0f1aa1137ba..7e73bca845a 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/full/functions/in/TestPlanExecutionForIn.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/full/functions/in/TestPlanExecutionForIn.java @@ -51,6 +51,17 @@ public class TestPlanExecutionForIn extends AlloyTestServer "{\n" + " name : String[1];\n" + " street : String[0..1];\n" + + "}\n" + + "Class test::Location\n" + + "{\n" + + " id: String[1];\n" + + " city: test::LocationEnum[1];\n" + + "}\n" + + "Enum test::LocationEnum\n" + + "{\n" + + " AMERICAS,\n" + + " EMEA,\n" + + " ASIA_PACIFIC\n" + "}\n"; public static final String STORE_MODEL = "###Relational\n" + @@ -70,6 +81,10 @@ public class TestPlanExecutionForIn extends AlloyTestServer " name VARCHAR(255) PRIMARY KEY,\n" + " address_name VARCHAR(255) \n" + " )\n" + + " Table Location(\n" + + " id VARCHAR(200) PRIMARY KEY,\n" + + " city VARCHAR(200) \n" + + " )\n" + "Join Address_Street(Address.name = Street.address_name)\n" + ")\n\n\n"; @@ -93,6 +108,22 @@ public class TestPlanExecutionForIn extends AlloyTestServer " name: [test::DB]Address.name,\n" + " street: [test::DB]@Address_Street | Street.name\n" + " }\n" + + " test::Location: Relational\n" + + " {\n" + + " ~primaryKey\n" + + " (\n" + + " [test::DB]Location.id\n" + + " )\n" + + " ~mainTable [test::DB]Location\n" + + " id: [test::DB]Location.id,\n" + + " city: EnumerationMapping LocationEnumMapping: [test::DB]Location.city\n" + + " }\n" + + "test::LocationEnum: EnumerationMapping LocationEnumMapping\n" + + " {\n" + + " AMERICAS: ['NewYork', 'Dallas'],\n" + + " EMEA: ['Paris', 'Stockholm'],\n" + + " ASIA_PACIFIC: ['Singapore', 'HongKong']\n" + + " }\n" + ")\n\n\n"; public static final String RUNTIME = "###Runtime\n" + @@ -146,6 +177,16 @@ protected void insertTestData(Statement statement) throws SQLException statement.execute("create schema user_view;"); statement.execute("Create Table user_view.UV_User_Roles_Public(CptyrRole VARCHAR(128) NOT NULL,RoleOrder INT NULL,UserID VARCHAR(32) NOT NULL, PRIMARY KEY(CptyrRole,UserID));"); statement.execute("Create Table user_view.UV_INQUIRY__PL_CADM(rpt_inq_oid BIGINT NOT NULL, rpt_inq_sourceinquiryid VARCHAR(200) NULL, PRIMARY KEY(rpt_inq_oid));"); + + statement.execute("Drop table if exists Location;"); + statement.execute("Create Table Location(id VARCHAR(200) NOT NULL, city VARCHAR(200))"); + statement.execute("insert into Location (id, city) values ('L1','NewYork');"); + statement.execute("insert into Location (id, city) values ('L2','Paris');"); + statement.execute("insert into Location (id, city) values ('L3','NewYork');"); + statement.execute("insert into Location (id, city) values ('L4','Singapore');"); + statement.execute("insert into Location (id, city) values ('L5','Singapore');"); + statement.execute("insert into Location (id, city) values ('L6','Dallas');"); + statement.execute("insert into Location (id, city) values ('L7','SomewhereElse');"); } @Test @@ -364,6 +405,31 @@ public void testInExecutionWithDateTimeListAndTimeZone() throws JsonProcessingEx Assert.assertEquals(expectedResWithMultipleValues, RelationalResultToJsonDefaultSerializer.removeComment(executePlan(plan, paramWithMultipleValues))); } + @Test + public void testInExecutionWithEnumList() throws JsonProcessingException + { + String fetchFunction = "###Pure\n" + + "function test::fetch(): Any[1]\n" + + "{\n" + + " {cityList:test::LocationEnum[*] | test::Location.all()\n" + + " ->filter(l:test::Location[1] | $l.city->in($cityList))\n" + + " ->project([x | $x.id], ['Id'])}\n" + + "}"; + + SingleExecutionPlan plan = buildPlanForFetchFunction(fetchFunction, false); + Map paramWithEmptyList = Maps.mutable.with("cityList", Lists.mutable.empty()); + Map paramWithSingleValue = Maps.mutable.with("cityList", Lists.mutable.with("AMERICAS")); + Map paramWithMultipleValues = Maps.mutable.with("cityList", Lists.mutable.with("AMERICAS", "ASIA_PACIFIC")); + + String expectedResWithEmptyList = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"Id\",\"type\":\"String\",\"relationalType\":\"VARCHAR(200)\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"root\\\".id as \\\"Id\\\" from Location as \\\"root\\\" where (0 = 1)\"}],\"result\":{\"columns\":[\"Id\"],\"rows\":[]}}"; + String expectedResWithSingleValue = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"Id\",\"type\":\"String\",\"relationalType\":\"VARCHAR(200)\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"root\\\".id as \\\"Id\\\" from Location as \\\"root\\\" where (\\\"root\\\".city in ('NewYork', 'Dallas'))\"}],\"result\":{\"columns\":[\"Id\"],\"rows\":[{\"values\":[\"L1\"]},{\"values\":[\"L3\"]},{\"values\":[\"L6\"]}]}}"; + String expectedResWithMultipleValues = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"Id\",\"type\":\"String\",\"relationalType\":\"VARCHAR(200)\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"root\\\".id as \\\"Id\\\" from Location as \\\"root\\\" where (\\\"root\\\".city in ('NewYork', 'Dallas', 'Singapore', 'HongKong'))\"}],\"result\":{\"columns\":[\"Id\"],\"rows\":[{\"values\":[\"L1\"]},{\"values\":[\"L3\"]},{\"values\":[\"L4\"]},{\"values\":[\"L5\"]},{\"values\":[\"L6\"]}]}}"; + + Assert.assertEquals(expectedResWithEmptyList, RelationalResultToJsonDefaultSerializer.removeComment(executePlan(plan, paramWithEmptyList))); + Assert.assertEquals(expectedResWithSingleValue, RelationalResultToJsonDefaultSerializer.removeComment(executePlan(plan, paramWithSingleValue))); + Assert.assertEquals(expectedResWithMultipleValues, RelationalResultToJsonDefaultSerializer.removeComment(executePlan(plan, paramWithMultipleValues))); + } + @Test public void testTempTableFlowWithPostProcessor() { diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/protocols/pure/v1_33_0/extension/extension_relational.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/protocols/pure/v1_33_0/extension/extension_relational.pure index 1abaf90b232..1b8065f5216 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/protocols/pure/v1_33_0/extension/extension_relational.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/protocols/pure/v1_33_0/extension/extension_relational.pure @@ -247,7 +247,7 @@ function meta::protocols::pure::v1_33_0::extension::getRelationalExtension():met name = $c.name, doc = $c.documentation, type = $c.type->toOne()->elementToPath(), - enumMapping = if($c.enumMappingId->isEmpty(),|[],|meta::protocols::pure::v1_33_0::transformation::fromPureGraph::executionPlan::transformEnumMapping($mapping->enumerationMappingByName($c.enumMappingId->toOne())->toOne())), + enumMapping = if($c.enumMappingId->isEmpty() || $mapping->enumerationMappingByName($c.enumMappingId->toOne())->isEmpty(),|[],|meta::protocols::pure::v1_33_0::transformation::fromPureGraph::executionPlan::transformEnumMapping($mapping->enumerationMappingByName($c.enumMappingId->toOne())->toOne())), relationalType = $c.sourceDataType->match( [ d:meta::relational::metamodel::datatype::DataType[0]|[], diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure index e3de3e0caa7..fad05c87c6f 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure @@ -243,9 +243,9 @@ function meta::relational::mapping::addEnumMapSupportFunctions(sq:meta::pure::ma { let enumParam = $sq.inScopeVars->keyValues().second->map(p | $p.values)->filter(e | $e->instanceOf(PlanVarPlaceHolder))->map(p | $p->cast(@PlanVarPlaceHolder).genericType.rawType->toOne())->filter(e | $e->instanceOf(Enumeration)); let enumMapTemplateFunctions = if($enumParam->isNotEmpty(), - |$m->toOne()->allEnumerationMappings()->filter(e | $e.enumeration->in($enumParam))->map(enum | let concatStr = if($enum.enumValueMappings->at(0).sourceValues->type() == String, |'\'', |''); - let enumHashMap = $enum.enumValueMappings->map(e | '"'+ $e.enum->cast(@Enum).name +'":"'+ $concatStr + $e.sourceValues->makeString('\', \'') + $concatStr + '"')->makeString(', '); - '<#function enumMap_' + fetchEnumFullPath($enum) + ' inputVal> <#assign enumMap = {'->concatenate($enumHashMap)->concatenate('}> <#if inputVal?has_content> <#return enumMap[inputVal]> <#else> <#return ""> ')->makeString(' ');), + |$m->toOne()->allEnumerationMappings()->filter(e | $e.enumeration->in($enumParam);)->map(enum | let concatStr = if($enum.enumValueMappings->at(0).sourceValues->type() == String, |'\'', |''); + let enumHashMap = $enum.enumValueMappings->map(e | '"'+ $e.enum.name +'":"'+ $concatStr + $e.sourceValues->makeString('\', \'') + $concatStr + '"')->makeString(', '); + meta::relational::mapping::createEnumSupportFunctions(fetchEnumFullPath($enum), $enumHashMap);), |[]); } @@ -259,6 +259,23 @@ function meta::relational::mapping::relationalPlanSupportFunctions(c:meta::exter if($c.timeZone->isNotEmpty() && !meta::pure::functions::date::systemDefaultTimeZones()->contains($c.timeZone->toOne()),|utcToTimeZoneSupportFunction(),|[])->concatenate(collectionRenderFunction())->concatenate(collectionSizeFunction())->concatenate(optionalVarPlaceHolderOperationSelectorFunction())->concatenate(varPlaceHolderToStringFunction())->concatenate(equalEnumOperationSelectorFunction()); } +function meta::relational::mapping::createEnumSupportFunctions(enumFullPath: String[1], enumHashMap: String[1]):String[*] +{ + '<#function enumMap_' + $enumFullPath + ' inputVal>' + + '<#assign enumMap = {' + $enumHashMap + '}>' + + '<#if inputVal?has_content>' + + '<#if inputVal?is_sequence>' + + '<#assign results = []>' + + '<#list inputVal as item>' + + '<#assign results += [enumMap[item]!""]>' + + '<#return results?join(", ")>' + + '<#else>' + + '<#return enumMap[inputVal]!"">' + + '<#else>' + + '<#return ""> ' + + ' '; +} + function meta::relational::mapping::varPlaceHolderToStringFunction():String[*] { '<#function varPlaceHolderToString optionalParameter prefix suffix replacementMap defaultValue>' + diff --git a/legend-engine-xts-service/legend-engine-test-runner-service/src/test/java/org/finos/legend/engine/testable/service/TestServiceTestSuite.java b/legend-engine-xts-service/legend-engine-test-runner-service/src/test/java/org/finos/legend/engine/testable/service/TestServiceTestSuite.java index 09ee82d66bc..149493622de 100644 --- a/legend-engine-xts-service/legend-engine-test-runner-service/src/test/java/org/finos/legend/engine/testable/service/TestServiceTestSuite.java +++ b/legend-engine-xts-service/legend-engine-test-runner-service/src/test/java/org/finos/legend/engine/testable/service/TestServiceTestSuite.java @@ -3126,6 +3126,19 @@ public void testPassingRelationalWithEnumParams() Assert.assertEquals(TestExecutionStatus.PASS, ((TestExecuted) testResult).testExecutionStatus); } + @Test + public void testPassingRelationalWithEnumListParams() + { + // setup + List relationalTestResult = executeServiceTest("testable/relational/", "legend-testable-relational-model.pure", "legend-testable-relational-service-enum-list-parameters.pure", "service::RelationalServiceWithEnumListParams"); + // Assertions + Assert.assertEquals(relationalTestResult.size(), 1); + TestResult testResult = relationalTestResult.get(0); + Assert.assertEquals(testResult.testable, "service::RelationalServiceWithEnumListParams"); + Assert.assertTrue(testResult instanceof TestExecuted); + Assert.assertEquals(TestExecutionStatus.PASS, ((TestExecuted) testResult).testExecutionStatus); + } + @Test public void testPassingRelationalWithSpecialEmbeddedData() { diff --git a/legend-engine-xts-service/legend-engine-test-runner-service/src/test/resources/testable/relational/legend-testable-relational-service-enum-list-parameters.pure b/legend-engine-xts-service/legend-engine-test-runner-service/src/test/resources/testable/relational/legend-testable-relational-service-enum-list-parameters.pure new file mode 100644 index 00000000000..d314b15c65a --- /dev/null +++ b/legend-engine-xts-service/legend-engine-test-runner-service/src/test/resources/testable/relational/legend-testable-relational-service-enum-list-parameters.pure @@ -0,0 +1,75 @@ +###Service +Service service::RelationalServiceWithEnumListParams +{ + pattern: '/myService/{eList}'; + documentation: ''; + autoActivateUpdates: true; + execution: Single + { + query: eList: model::EmployeeType[*]|model::Person.all()->filter( + p|$p.employeeType->in( + $eList + ) + )->project( + [ + x|$x.firstName, + x|$x.lastName + ], + [ + 'First Name', + 'Last Name' + ] + ); + mapping: execution::FirmMapping; + runtime: execution::Runtime; + } + testSuites: + [ + testSuite1: + { + data: + [ + connections: + [ + model::MyConnection: + Reference + #{ + data::RelationalData + }#, + model::MyConnection: + DataspaceTestData + #{ + model::com::PersonDataspace + }# + ] + ] + tests: + [ + test1: + { + serializationFormat: PURE_TDSOBJECT; + parameters: + [ + eList = [ + model::EmployeeType.FULL_TIME, + model::EmployeeType.CONTRACT +] + ] + asserts: + [ + shouldPass: + EqualToJson + #{ + expected: + ExternalFormat + #{ + contentType: 'application/json'; + data: '[ {\n "First Name" : "John",\n "Last Name" : "Doe"\n}, {\n "First Name" : "Nicole",\n "Last Name" : "Smith"\n}, {\n "First Name" : "Time",\n "Last Name" : "Smith"\n} ]'; + }#; + }# + ] + } + ] + } + ] +} \ No newline at end of file