Repository: finos/legend-studio (ts)

Issue:
Bug: Missing support for local properties in relational mappings
### Similar issues

- [X] I have searched and found no existing similar issues

### How are you using Studio?

Legend Studio

### Current and expected behavior

Legend Engine allows users to define local properties in context of mapping. These properties are specifically useful in context of cross store relationships where adding the property in model is not ideal for modelling the business concept.

Engine supports local properties in context of relational mapping but studio does not support it yet.

Note : Studio does support local properties in context of M2M mapping

Below code currently throws an error - ```Can't build graph: Local mapping property is not supported```
Ideally studio should be able to compile below code

### Steps to reproduce

_No response_

### Model data

```text
###Relational
Database demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb
(
  Table productTable
  (
    productId VARCHAR(100) PRIMARY KEY,
    productName VARCHAR(100),
    description VARCHAR(1000)
  )
  Table synonymTable
  (
    synonymId INTEGER PRIMARY KEY,
    productId VARCHAR(100),
    type VARCHAR(200),
    name VARCHAR(200)
  )

  Join productTable_synonymTable(productTable.productId = synonymTable.productId)
)

###Pure

Enum demo::XStore::graphFetch::inMemoryAndRelational::models::SynonymType
{
  CUSIP,
  ISIN
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::T_Trade
{
  prodId: String[1];
  tradeId: Integer[1];
  traderKerb: String[0..1];
  quantity: Integer[1];
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::S_TradesWrapper
{
  s_trades: demo::XStore::graphFetch::inMemoryAndRelational::models::S_Trade[*];
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::Trade
{
  tradeId: Integer[1];
  traderKerb: String[0..1];
  quantity: Integer[1];
  productName() {$this.product.productName}: String[0..1];
  justProduct() {$this.product}: demo::XStore::graphFetch::inMemoryAndRelational::models::Product[0..1];
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::Product
{
  productId: String[1];
  productName: String[1];
  description: String[1];
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::TradeEvent
{
  eventId: String[1];
  description: String[1];
  timestamp: DateTime[1];
}


Class demo::XStore::graphFetch::inMemoryAndRelational::models::S_Trade
{
  s_tradeId: Integer[1];
  s_traderDetails: String[1];
  s_tradeDetails: String[1];
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::Synonym
{
  name: String[1];
  type: demo::XStore::graphFetch::inMemoryAndRelational::models::SynonymType[1];
}

Class demo::XStore::graphFetch::inMemoryAndRelational::models::Trader
{
  kerberos: String[1];
  firstName: String[1];
  lastName: String[1];
}

Association demo::XStore::graphFetch::inMemoryAndRelational::models::Trade_Trader
{
  trader: demo::XStore::graphFetch::inMemoryAndRelational::models::Trader[1];
  trades: demo::XStore::graphFetch::inMemoryAndRelational::models::Trade[*];
}
Association demo::XStore::graphFetch::inMemoryAndRelational::models::T_Trade_Product
{
  product: demo::XStore::graphFetch::inMemoryAndRelational::models::Product[1];
  t_trades: demo::XStore::graphFetch::inMemoryAndRelational::models::T_Trade[*];
}

Association demo::XStore::graphFetch::inMemoryAndRelational::models::Product_Synonym
{
  product: demo::XStore::graphFetch::inMemoryAndRelational::models::Product[1];
  synonyms: demo::XStore::graphFetch::inMemoryAndRelational::models::Synonym[*];
}

Association demo::XStore::graphFetch::inMemoryAndRelational::models::Trade_Product
{
  product: demo::XStore::graphFetch::inMemoryAndRelational::models::Product[1];
  trades: demo::XStore::graphFetch::inMemoryAndRelational::models::Trade[*];
}

Association demo::XStore::graphFetch::inMemoryAndRelational::models::Trade_TradeEvent
{
  tradeEvents: demo::XStore::graphFetch::inMemoryAndRelational::models::TradeEvent[*];
  trade: demo::XStore::graphFetch::inMemoryAndRelational::models::Trade[1];
}
Association demo::XStore::graphFetch::inMemoryAndRelational::models::T_Trade_Trader
{
  trader: demo::XStore::graphFetch::inMemoryAndRelational::models::Trader[1];
  t_trades: demo::XStore::graphFetch::inMemoryAndRelational::models::T_Trade[*];
}


###Mapping
Mapping demo::XStore::graphFetch::inMemoryAndRelational::mappings::InMemoryAndRelationalCrossStoreMapping1
(
  demo::XStore::graphFetch::inMemoryAndRelational::models::Trade[trade_set]: Pure
  {
    ~src demo::XStore::graphFetch::inMemoryAndRelational::models::S_Trade
    tradeId: $src.s_tradeId,
    +prodId: String[1]: $src.s_tradeDetails->split(':')->at(0),
    quantity: $src.s_tradeDetails->split(':')->at(1)->parseInteger()
  }
  demo::XStore::graphFetch::inMemoryAndRelational::models::Product[prod_set]: Relational
  {
    ~primaryKey
    (
      [demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb]productTable.productId
    )
    ~mainTable [demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb]productTable

    +local : String[1] : [demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb]productTable.productName,
    productId: [demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb]productTable.productId,
    productName: [demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb]productTable.productName,
    description: [demo::XStore::graphFetch::inMemoryAndRelational::database::inMemoryAndRelationalDb]productTable.description
  }

  demo::XStore::graphFetch::inMemoryAndRelational::models::Trade_Product: XStore
  {
    product[trade_set, prod_set]: $this.prodId == $that.productId,
    trades[prod_set, trade_set]: $this.productId == $that.prodId
  }
)
```


### Environment

```text
(no information provided)
```


### Possible solution and workaround

_No response_

### Contribution

- [ ] I would like to work on the fix for this issue

Pull request description:
Fix transformer of propertyOwner for local properties in relational mapping and pure mapping


<!--
  Thanks for submitting a pull request!
  We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request.

  Before submitting a pull request, please make sure the following is done:
  1. Fork [the repository](https://github.com/finos/legend-studio) and create your branch from `master`.
  2. Run `yarn` in the repository root.
  3. If you've fixed a bug or added code that should be tested, add tests!
  4. Add a changeset to summarize your change in the changelogs.
  5. If you haven't already, complete the CLA.

  Learn more about contributing: https://github.com/finos/legend-studio/blob/master/CONTRIBUTING.md
-->

## Summary

Closes #773 
<!--
  Explain the **motivation** for making this change. What existing problem(s) does this PR solve?
  Also, link the issue(s) to this PR: e.g. Fixes #1, Resolves #2
  If you also added documentation, link the PR from https://github.com/finos/legend
-->

## How did you test this change?

- [ ] Test(s) added
- [ ] Manual testing (please provide screenshots/recordings)
- [x] No testing (please provide an explanation)

<!--
  Demonstrate the code is solid. Screenshots/videos if the pull request changes the user interface.
  How exactly did you verify that your PR solves the issue you wanted to solve?
  If you leave this empty, your PR will very likely be closed.
-->

<!--
  More in-depth docs below:
  - Git workflow: https://github.com/finos/legend-studio/blob/master/docs/workflow/working-with-github.md
  - Test: https://github.com/finos/legend-studio/blob/master/docs/technical/test-strategy.md
  - Changeset: https://github.com/finos/legend-studio/blob/master/CONTRIBUTING.md#changeset
  - Dependency: https://github.com/finos/legend-studio/blob/master/docs/workflow/dependencies.md
  - UX/UI: https://github.com/finos/legend-studio/tree/master/docs/ux
-->


Patch:
diff --git a/.changeset/brown-drinks-smoke.md b/.changeset/brown-drinks-smoke.md
new file mode 100644
index 000000000..d4ab669a4
--- /dev/null
+++ b/.changeset/brown-drinks-smoke.md
@@ -0,0 +1,3 @@
+---
+"@finos/legend-extension-dsl-diagram": patch
+---
diff --git a/.changeset/eighty-tomatoes-beg.md b/.changeset/eighty-tomatoes-beg.md
new file mode 100644
index 000000000..962f0c050
--- /dev/null
+++ b/.changeset/eighty-tomatoes-beg.md
@@ -0,0 +1,5 @@
+---
+"@finos/legend-graph": patch
+---
+
+Exclude class in property pointer during transformation for local property mapping.
diff --git a/.changeset/red-beers-fail.md b/.changeset/red-beers-fail.md
new file mode 100644
index 000000000..108af8bbf
--- /dev/null
+++ b/.changeset/red-beers-fail.md
@@ -0,0 +1,5 @@
+---
+"@finos/legend-query": patch
+---
+
+Add parameter instance values when executing unsupported queries.
\ No newline at end of file
diff --git a/packages/legend-extension-dsl-diagram/src/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.ts b/packages/legend-extension-dsl-diagram/src/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.ts
index b7fdfd1aa..66c54bd17 100644
--- a/packages/legend-extension-dsl-diagram/src/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.ts
+++ b/packages/legend-extension-dsl-diagram/src/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.ts
@@ -54,7 +54,7 @@ const transformPropertyView = (element: PropertyView): V1_PropertyView => {
   const line = new V1_Line();
   line.points = element.fullPath;
   view.line = line;
-  view.property = V1_transformPropertyReference(element.property, false);
+  view.property = V1_transformPropertyReference(element.property);
   view.sourceView = relationshipEdgeViewTransformer(element.from);
   view.targetView = relationshipEdgeViewTransformer(element.to);
   return view;
diff --git a/packages/legend-graph/src/models/protocols/pure/v1/transformation/pureGraph/from/V1_MappingTransformer.ts b/packages/legend-graph/src/models/protocols/pure/v1/transformation/pureGraph/from/V1_MappingTransformer.ts
index d186340b9..a47e46ab5 100644
--- a/packages/legend-graph/src/models/protocols/pure/v1/transformation/pureGraph/from/V1_MappingTransformer.ts
+++ b/packages/legend-graph/src/models/protocols/pure/v1/transformation/pureGraph/from/V1_MappingTransformer.ts
@@ -147,12 +147,17 @@ import type { SubstituteStore } from '../../../../../../metamodels/pure/packagea
 
 export const V1_transformPropertyReference = (
   element: PropertyReference,
-  isTransformingEmbeddedPropertyMapping: boolean,
+  options?: {
+    isTransformingEmbeddedPropertyMapping?: boolean;
+    isTransformingLocalPropertyMapping?: boolean;
+  },
 ): V1_PropertyPointer => {
   const property = new V1_PropertyPointer();
-  property.class = isTransformingEmbeddedPropertyMapping
-    ? undefined
-    : V1_transformElementReference(element.ownerReference);
+  property.class =
+    options?.isTransformingEmbeddedPropertyMapping ||
+    options?.isTransformingLocalPropertyMapping
+      ? undefined
+      : V1_transformElementReference(element.ownerReference);
   property.property = element.value.name;
   return property;
 };
@@ -372,7 +377,6 @@ const transformSimpleFlatDataPropertyMapping = (
     transformOptionalPropertyMappingTransformer(element.transformer);
   flatDataPropertyMapping.property = V1_transformPropertyReference(
     element.property,
-    false,
   );
   flatDataPropertyMapping.source = transformPropertyMappingSource(
     element.sourceSetImplementation,
@@ -399,10 +403,7 @@ const transformEmbeddedFlatDataPropertyMapping = (
     embedded.id = id;
   }
   embedded.class = V1_transformElementReference(element.class);
-  embedded.property = V1_transformPropertyReference(
-    element.property,
-    false, // TODO: we might ned to turn this on in the future once we start working on the gramar roundtrip for flat-data
-  );
+  embedded.property = V1_transformPropertyReference(element.property); // TODO: we might ned to turn 'isTransformingEmbeddedPropertyMapping' on in the future once we start working on the gramar roundtrip for flat-data
   embedded.propertyMappings = transformClassMappingPropertyMappings(
     element.propertyMappings,
     false, // TODO: we might ned to turn this on in the future once we start working on the gramar roundtrip for flat-data
@@ -428,7 +429,9 @@ const transformPurePropertyMapping = (
     transformOptionalPropertyMappingTransformer(element.transformer);
   purePropertyMapping.property = V1_transformPropertyReference(
     element.property,
-    false,
+    {
+      isTransformingLocalPropertyMapping: Boolean(element.localMappingProperty),
+    },
   );
   purePropertyMapping.source = ''; // @MARKER: GRAMMAR ROUNDTRIP --- omit this information during protocol transformation as it can be interpreted while building the graph
   purePropertyMapping.target = transformPropertyMappingTarget(
@@ -459,10 +462,11 @@ const transformRelationalPropertyMapping = (
   propertyMapping.enumMappingId = transformOptionalPropertyMappingTransformer(
     element.transformer,
   );
-  propertyMapping.property = V1_transformPropertyReference(
-    element.property,
-    isTransformingEmbeddedPropertyMapping,
-  );
+  propertyMapping.property = V1_transformPropertyReference(element.property, {
+    isTransformingEmbeddedPropertyMapping:
+      isTransformingEmbeddedPropertyMapping,
+    isTransformingLocalPropertyMapping: Boolean(element.localMappingProperty),
+  });
   // Prune source information from the operation
   // NOTE: if in the future, source information is stored under different key,
   // e.g. { "classPointerSourceInformation": ... }
@@ -496,10 +500,10 @@ const transformEmbeddedRelationalPropertyMapping = (
   context: V1_GraphTransformerContext,
 ): V1_EmbeddedRelationalPropertyMapping => {
   const embedded = new V1_EmbeddedRelationalPropertyMapping();
-  embedded.property = V1_transformPropertyReference(
-    element.property,
-    isTransformingEmbeddedPropertyMapping,
-  );
+  embedded.property = V1_transformPropertyReference(element.property, {
+    isTransformingEmbeddedPropertyMapping:
+      isTransformingEmbeddedPropertyMapping,
+  });
   embedded.source = undefined; // @MARKER: GRAMMAR ROUNDTRIP --- omit this information during protocol transformation as it can be interpreted while building the graph
   embedded.target = transformPropertyMappingTarget(
     element.targetSetImplementation,
@@ -533,10 +537,10 @@ const transformInlineEmbeddedRelationalPropertyMapping = (
   isTransformingEmbeddedPropertyMapping: boolean,
 ): V1_InlineEmbeddedPropertyMapping => {
   const embedded = new V1_InlineEmbeddedPropertyMapping();
-  embedded.property = V1_transformPropertyReference(
-    element.property,
-    isTransformingEmbeddedPropertyMapping,
-  );
+  embedded.property = V1_transformPropertyReference(element.property, {
+    isTransformingEmbeddedPropertyMapping:
+      isTransformingEmbeddedPropertyMapping,
+  });
   embedded.source = undefined; // @MARKER: GRAMMAR ROUNDTRIP --- omit this information during protocol transformation as it can be interpreted while building the graph
   embedded.target = transformPropertyMappingTarget(
     element.targetSetImplementation,
@@ -556,10 +560,10 @@ const transformOtherwiseEmbeddedRelationalPropertyMapping = (
   context: V1_GraphTransformerContext,
 ): V1_OtherwiseEmbeddedRelationalPropertyMapping => {
   const embedded = new V1_OtherwiseEmbeddedRelationalPropertyMapping();
-  embedded.property = V1_transformPropertyReference(
-    element.property,
-    isTransformingEmbeddedPropertyMapping,
-  );
+  embedded.property = V1_transformPropertyReference(element.property, {
+    isTransformingEmbeddedPropertyMapping:
+      isTransformingEmbeddedPropertyMapping,
+  });
   embedded.source = undefined; // @MARKER: GRAMMAR ROUNDTRIP --- omit this information during protocol transformation as it can be interpreted while building the graph
   embedded.target = transformPropertyMappingTarget(
     element.targetSetImplementation,
@@ -601,7 +605,7 @@ const transformXStorePropertyMapping = (
   context: V1_GraphTransformerContext,
 ): V1_XStorePropertyMapping => {
   const xstore = new V1_XStorePropertyMapping();
-  xstore.property = V1_transformPropertyReference(element.property, false);
+  xstore.property = V1_transformPropertyReference(element.property);
   xstore.source = transformPropertyMappingSource(
     element.sourceSetImplementation,
   );
@@ -626,10 +630,7 @@ const transformAggregationAwarePropertyMapping = (
   element: AggregationAwarePropertyMapping,
 ): V1_AggregationAwarePropertyMapping => {
   const propertyMapping = new V1_AggregationAwarePropertyMapping();
-  propertyMapping.property = V1_transformPropertyReference(
-    element.property,
-    false,
-  );
+  propertyMapping.property = V1_transformPropertyReference(element.property);
   propertyMapping.source = transformPropertyMappingSource(
     element.sourceSetImplementation,
   );
diff --git a/packages/legend-query/src/stores/QueryBuilderLambdaBuilder.ts b/packages/legend-query/src/stores/QueryBuilderLambdaBuilder.ts
index e8f775e61..d297c1474 100644
--- a/packages/legend-query/src/stores/QueryBuilderLambdaBuilder.ts
+++ b/packages/legend-query/src/stores/QueryBuilderLambdaBuilder.ts
@@ -70,6 +70,45 @@ export const buildGetAllFunction = (
   return _func;
 };
 
+export const buildParametersLetLambdaFunc = (
+  queryBuilderState: QueryBuilderState,
+): LambdaFunction => {
+  const multiplicityOne =
+    queryBuilderState.graphManagerState.graph.getTypicalMultiplicity(
+      TYPICAL_MULTIPLICITY_TYPE.ONE,
+    );
+  const typeString = queryBuilderState.graphManagerState.graph.getPrimitiveType(
+    PRIMITIVE_TYPE.STRING,
+  );
+  const typeAny = queryBuilderState.graphManagerState.graph.getType(
+    CORE_PURE_PATH.ANY,
+  );
+  const letlambdaFunction = new LambdaFunction(
+    new FunctionType(typeAny, multiplicityOne),
+  );
+  letlambdaFunction.expressionSequence =
+    queryBuilderState.queryParametersState.parameters
+      .map((_var) => {
+        if (_var.values) {
+          const letFunc = new SimpleFunctionExpression(
+            extractElementNameFromPath(SUPPORTED_FUNCTIONS.LET),
+            multiplicityOne,
+          );
+          const letVar = new PrimitiveInstanceValue(
+            GenericTypeExplicitReference.create(new GenericType(typeString)),
+            multiplicityOne,
+          );
+          letVar.values = [_var.variableName];
+          letFunc.parametersValues.push(letVar);
+          letFunc.parametersValues.push(_var.values);
+          return letFunc;
+        }
+        return undefined;
+      })
+      .filter(isNonNullable);
+  return letlambdaFunction;
+};
+
 export const buildLambdaFunction = (
   queryBuilderState: QueryBuilderState,
   options?: {
@@ -378,27 +417,9 @@ export const buildLambdaFunction = (
     // add let statements for each parameter
     if (options?.isBuildingExecutionQuery) {
       lambdaFunction.functionType.parameters = [];
-      const letsFuncs = queryBuilderState.queryParametersState.parameters
-        .map((_var) => {
-          if (_var.values) {
-            const letFunc = new SimpleFunctionExpression(
-              extractElementNameFromPath(SUPPORTED_FUNCTIONS.LET),
-              multiplicityOne,
-            );
-            const letVar = new PrimitiveInstanceValue(
-              GenericTypeExplicitReference.create(new GenericType(typeString)),
-              multiplicityOne,
-            );
-            letVar.values = [_var.variableName];
-            letFunc.parametersValues.push(letVar);
-            letFunc.parametersValues.push(_var.values);
-            return letFunc;
-          }
-          return undefined;
-        })
-        .filter(isNonNullable);
+      const letsFuncs = buildParametersLetLambdaFunc(queryBuilderState);
       lambdaFunction.expressionSequence = [
-        ...letsFuncs,
+        ...letsFuncs.expressionSequence,
         ...lambdaFunction.expressionSequence,
       ];
     } else {
diff --git a/packages/legend-query/src/stores/QueryBuilderResultState.ts b/packages/legend-query/src/stores/QueryBuilderResultState.ts
index 0015ea29b..a4fd9d33b 100644
--- a/packages/legend-query/src/stores/QueryBuilderResultState.ts
+++ b/packages/legend-query/src/stores/QueryBuilderResultState.ts
@@ -29,7 +29,10 @@ import {
   GRAPH_MANAGER_LOG_EVENT,
   PureClientVersion,
 } from '@finos/legend-graph';
-import { buildLambdaFunction } from './QueryBuilderLambdaBuilder';
+import {
+  buildLambdaFunction,
+  buildParametersLetLambdaFunc,
+} from './QueryBuilderLambdaBuilder';
 
 const DEFAULT_LIMIT = 1000;
 
@@ -91,6 +94,26 @@ export class QueryBuilderResultState {
           this.queryBuilderState.queryUnsupportedState.rawLambda,
           'Lambda is required to execute query',
         );
+        if (
+          !this.queryBuilderState.mode.isParametersDisabled &&
+          this.queryBuilderState.queryParametersState.parameters.length
+        ) {
+          const letlambdaFunction = buildParametersLetLambdaFunc(
+            this.queryBuilderState,
+          );
+          const letRawLambda =
+            this.queryBuilderState.buildRawLambdaFromLambdaFunction(
+              letlambdaFunction,
+            );
+          // reset paramaters
+          if (Array.isArray(query.body) && Array.isArray(letRawLambda.body)) {
+            letRawLambda.body = [
+              ...(letRawLambda.body as object[]),
+              ...(query.body as object[]),
+            ];
+            query = letRawLambda;
+          }
+        }
       }
       const result =
         (yield this.queryBuilderState.graphManagerState.graphManager.executeMapping(


Test patch:
diff --git a/packages/legend-graph/src/__tests__/roundtripTestData/TEST_DATA__MappingRoundtrip.ts b/packages/legend-graph/src/__tests__/roundtripTestData/TEST_DATA__MappingRoundtrip.ts
index 181eba3a6..add590f0a 100644
--- a/packages/legend-graph/src/__tests__/roundtripTestData/TEST_DATA__MappingRoundtrip.ts
+++ b/packages/legend-graph/src/__tests__/roundtripTestData/TEST_DATA__MappingRoundtrip.ts
@@ -558,7 +558,6 @@ export const TEST_DATA__LocalPropertyMapping = [
                 type: 'Integer',
               },
               property: {
-                class: 'test::Person',
                 property: 'firmId',
               },
               source: '',
@@ -1212,7 +1211,6 @@ export const TEST_DATA__Relational_LocalPropertyMappingRoundtrip = [
                 type: 'String',
               },
               property: {
-                class: 'my::models::Product',
                 property: 'local',
               },
               relationalOperation: {