Skip to content

Commit

Permalink
Handle subquery alias as source for defined values in join
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Nov 15, 2023
1 parent 4c077ca commit c7c5b81
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 3 deletions.
117 changes: 117 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,77 @@ DECLARE @json NVARCHAR(4000) = N'{
}
}

[TestMethod]
public void OpenJsonDefaultSchemaDataTypes()
{
using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = @"
DECLARE @json NVARCHAR(2048) = N'{
""String_value"": ""John"",
""DoublePrecisionFloatingPoint_value"": 45,
""DoublePrecisionFloatingPoint_value"": 2.3456,
""BooleanTrue_value"": true,
""BooleanFalse_value"": false,
""Null_value"": null,
""Array_value"": [""a"",""r"",""r"",""a"",""y""],
""Object_value"": {""obj"":""ect""}
}';
SELECT * FROM OpenJson(@json);";

using (var reader = cmd.ExecuteReader())
{
Assert.AreEqual("key", reader.GetName(0));
Assert.AreEqual("value", reader.GetName(1));
Assert.AreEqual("type", reader.GetName(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("String_value", reader.GetString(0));
Assert.AreEqual("John", reader.GetString(1));
Assert.AreEqual(1, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("DoublePrecisionFloatingPoint_value", reader.GetString(0));
Assert.AreEqual("45", reader.GetString(1));
Assert.AreEqual(2, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("DoublePrecisionFloatingPoint_value", reader.GetString(0));
Assert.AreEqual("2.3456", reader.GetString(1));
Assert.AreEqual(2, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("BooleanTrue_value", reader.GetString(0));
Assert.AreEqual("true", reader.GetString(1));
Assert.AreEqual(3, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("BooleanFalse_value", reader.GetString(0));
Assert.AreEqual("false", reader.GetString(1));
Assert.AreEqual(3, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("Null_value", reader.GetString(0));
Assert.IsTrue(reader.IsDBNull(1));
Assert.AreEqual(0, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("Array_value", reader.GetString(0));
Assert.AreEqual("[\"a\",\"r\",\"r\",\"a\",\"y\"]", reader.GetString(1));
Assert.AreEqual(4, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("Object_value", reader.GetString(0));
Assert.AreEqual("{\"obj\":\"ect\"}", reader.GetString(1));
Assert.AreEqual(5, reader.GetInt32(2));

Assert.IsFalse(reader.Read());
}
}
}

[TestMethod]
public void OpenJsonExplicitSchema()
{
Expand Down Expand Up @@ -1682,5 +1753,51 @@ [Order] NVARCHAR(MAX) AS JSON
}
}
}

[TestMethod]
public void MergeJson()
{
using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = @"
DECLARE @json1 NVARCHAR(MAX),@json2 NVARCHAR(MAX)
SET @json1=N'{""name"": ""John"", ""surname"":""Doe""}'
SET @json2=N'{""name"": ""John"", ""age"":45}'
SELECT *
FROM OPENJSON(@json1)
UNION ALL
SELECT *
FROM OPENJSON(@json2)
WHERE [key] NOT IN (SELECT [key] FROM OPENJSON(@json1))";

using (var reader = cmd.ExecuteReader())
{
Assert.AreEqual("key", reader.GetName(0));
Assert.AreEqual("value", reader.GetName(1));
Assert.AreEqual("type", reader.GetName(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("name", reader.GetString(0));
Assert.AreEqual("John", reader.GetString(1));
Assert.AreEqual(1, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("surname", reader.GetString(0));
Assert.AreEqual("Doe", reader.GetString(1));
Assert.AreEqual(1, reader.GetInt32(2));

Assert.IsTrue(reader.Read());
Assert.AreEqual("age", reader.GetString(0));
Assert.AreEqual("45", reader.GetString(1));
Assert.AreEqual(2, reader.GetInt32(2));

Assert.IsFalse(reader.Read());
}
}
}
}
}
17 changes: 16 additions & 1 deletion MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseJoinNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ protected Entity Merge(Entity leftEntity, INodeSchema leftSchema, Entity rightEn
return merged;
}

protected void FoldDefinedValues(INodeSchema rightSchema)
{
foreach (var kvp in DefinedValues.ToList())
{
if (!rightSchema.ContainsColumn(kvp.Value, out var innerColumn))
throw new NotSupportedQueryFragmentException($"Unknown defined column '{kvp.Value}'");

if (innerColumn != kvp.Value)
DefinedValues[kvp.Key] = innerColumn;
}
}

public override IEnumerable<IExecutionPlanNode> GetSources()
{
yield return LeftSource;
Expand Down Expand Up @@ -172,7 +184,10 @@ protected virtual INodeSchema GetSchema(NodeCompilationContext context, bool inc
}

foreach (var definedValue in DefinedValues)
schema[definedValue.Key] = innerSchema.Schema[definedValue.Value];
{
innerSchema.ContainsColumn(definedValue.Value, out var innerColumn);
schema[definedValue.Key] = innerSchema.Schema[innerColumn];
}

_lastLeftSchema = outerSchema;
_lastRightSchema = innerSchema;
Expand Down
10 changes: 8 additions & 2 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext
RightSource = RightSource.FoldQuery(context, hints);
RightSource.Parent = this;

var leftSchema = LeftSource.GetSchema(context);
var rightSchema = RightSource.GetSchema(context);

FoldDefinedValues(rightSchema);

if (SemiJoin)
return this;

var leftSchema = LeftSource.GetSchema(context);
var rightSchema = RightSource.GetSchema(context);
var leftFilter = JoinType == QualifiedJoinType.Inner || JoinType == QualifiedJoinType.LeftOuter ? LeftSource as FilterNode : null;
var rightFilter = JoinType == QualifiedJoinType.Inner || JoinType == QualifiedJoinType.RightOuter ? RightSource as FilterNode : null;
var leftFetch = (leftFilter?.Source ?? LeftSource) as FetchXmlScan;
Expand Down Expand Up @@ -685,9 +688,12 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList<st

var leftColumns = requiredColumns
.Where(col => leftSchema.ContainsColumn(col, out _))
.Distinct()
.ToList();
var rightColumns = requiredColumns
.Where(col => rightSchema.ContainsColumn(col, out _))
.Concat(DefinedValues.Values)
.Distinct()
.ToList();

if (LeftAttribute != null)
Expand Down
3 changes: 3 additions & 0 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,12 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext

var innerParameterTypes = GetInnerParameterTypes(leftSchema, context.ParameterTypes);
var innerContext = new NodeCompilationContext(context.DataSources, context.Options, innerParameterTypes, context.Log);
var rightSchema = RightSource.GetSchema(innerContext);
RightSource = RightSource.FoldQuery(innerContext, hints);
RightSource.Parent = this;

FoldDefinedValues(rightSchema);

if (LeftSource is ConstantScanNode constant &&
constant.Schema.Count == 0 &&
constant.Values.Count == 1 &&
Expand Down

0 comments on commit c7c5b81

Please sign in to comment.