Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/nuget/MarkMpn.Sql4Cds.Engine.Ne…
Browse files Browse the repository at this point in the history
…tCore/System.Data.SqlClient-4.8.6
  • Loading branch information
MarkMpn authored Apr 8, 2024
2 parents 3278cdb + 3982f90 commit 4a86ce7
Show file tree
Hide file tree
Showing 73 changed files with 3,900 additions and 1,080 deletions.
15 changes: 13 additions & 2 deletions AzureDataStudioExtension/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# Change Log

## [v8.0.1](https://github.com/MarkMpn/Sql4Cds/releases/tag/v8.0.1) - 2023-12-13
## [v9.0.0](https://github.com/MarkMpn/Sql4Cds/releases/tag/v8.0.1) - 2024-04-12

Added support for latest Fetch XML features
Support `TRY`, `CATCH` & `THROW` statements and related functions
Error handling consistency with SQL Server
Improved performance with large numbers of expressions and large `VALUES` lists
Generate the execution plan for each statement in a batch only when necessary, to allow initial statements to succeed regardless of errors in later statements
Allow access to catalog views using TDS Endpoint
Inproved `EXECUTE AS` support
Handle missing values in XML `.value()` method
Detect TDS Endpoint incompatibility with XML data type methods and error handling functions
Fixed use of `TOP 1` with `IN` expression
Fixed escaping column names for `SELECT` and `INSERT` commands
Improved setting a partylist attribute based on an EntityReference value
Fixed sorting results for `UNION`
Fold `DISTNCT` to data source for `UNION`
Fold `DISTINCT` to data source for `UNION`
Fold groupings without aggregates to `DISTINCT`
Fixed folding filters through nested loops with outer references
Fixed use of recursive CTE references within subquery
Improved performance of `CROSS APPLY` and `OUTER APPLY`
Improved query cancellation

## [v8.0.0](https://github.com/MarkMpn/Sql4Cds/releases/tag/v8.0.0) - 2023-11-25

Expand Down
305 changes: 305 additions & 0 deletions MarkMpn.Sql4Cds.Engine.FetchXml.Tests/FetchXml2SqlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,40 @@ public void Order()
Assert.AreEqual("SELECT firstname, lastname FROM contact ORDER BY firstname ASC", NormalizeWhitespace(converted));
}

[TestMethod]
public void OrderPicklist()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='new_customentity'>
<attribute name='new_name' />
<order attribute='new_optionsetvalue' />
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions(), out _);

Assert.AreEqual("SELECT new_name FROM new_customentity ORDER BY new_optionsetvaluename ASC", NormalizeWhitespace(converted));
}

[TestMethod]
public void OrderPicklistRaw()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch useraworderby='1'>
<entity name='new_customentity'>
<attribute name='new_name' />
<order attribute='new_optionsetvalue' />
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions(), out _);

Assert.AreEqual("SELECT new_name FROM new_customentity ORDER BY new_optionsetvalue ASC", NormalizeWhitespace(converted));
}

[TestMethod]
public void OrderDescending()
{
Expand All @@ -132,6 +166,28 @@ public void OrderDescending()
Assert.AreEqual("SELECT firstname, lastname FROM contact ORDER BY firstname DESC", NormalizeWhitespace(converted));
}

[TestMethod]
public void JoinOrder()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='firstname' />
<attribute name='lastname' />
<link-entity name='account' from='accountid' to='parentcustomerid'>
<attribute name='name' />
</link-entity>
<order entityname='account' attribute='name' />
<order attribute='firstname' />
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions(), out _);

Assert.AreEqual("SELECT contact.firstname, contact.lastname, account.name FROM contact INNER JOIN account ON contact.parentcustomerid = account.accountid ORDER BY account.name ASC, contact.firstname ASC", NormalizeWhitespace(converted));
}

[TestMethod]
public void NoLock()
{
Expand Down Expand Up @@ -551,6 +607,255 @@ UNION ALL
SELECT * FROM account WHERE accountid IN ( SELECT accountid FROM account_hierarchical )"), NormalizeWhitespace(converted));
}

[TestMethod]
public void Exists()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='exists'>
<filter type='and'>
<condition attribute='statecode'
operator='eq'
value='1' />
</filter>
</link-entity>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT fullname FROM contact WHERE EXISTS( SELECT account.primarycontactid FROM account WHERE account.statecode = '1' AND contact.contactid = account.primarycontactid )"), NormalizeWhitespace(converted));
}

[TestMethod]
public void In()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='in'>
<filter type='and'>
<condition attribute='statecode'
operator='eq'
value='1' />
</filter>
</link-entity>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT fullname FROM contact WHERE contactid IN ( SELECT account.primarycontactid FROM account WHERE account.statecode = '1' )"), NormalizeWhitespace(converted));
}

[TestMethod]
public void MatchFirstRowUsingCrossApply()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='matchfirstrowusingcrossapply'>
<attribute name='accountid' />
<attribute name='name' />
</link-entity>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT contact.fullname, account.accountid, account.name FROM contact CROSS APPLY ( SELECT TOP 1 account.accountid, account.name FROM account WHERE contact.contactid = account.primarycontactid ) AS account"), NormalizeWhitespace(converted));
}

[TestMethod]
public void ColumnComparison()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact' >
<attribute name='firstname' />
<filter>
<condition attribute='firstname'
operator='eq'
valueof='lastname' />
</filter>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT firstname FROM contact WHERE firstname = lastname"), NormalizeWhitespace(converted));
}

[TestMethod]
public void ColumnComparisonCrossTable()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='contactid' />
<attribute name='fullname' />
<filter type='and'>
<condition attribute='fullname'
operator='eq'
valueof='acct.name' />
</filter>
<link-entity name='account'
from='accountid'
to='parentcustomerid'
link-type='outer'
alias='acct'>
<attribute name='name' />
</link-entity>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT contact.contactid, contact.fullname, acct.name FROM contact LEFT OUTER JOIN account AS acct ON contact.parentcustomerid = acct.accountid WHERE contact.fullname = acct.name"), NormalizeWhitespace(converted));
}

[TestMethod]
public void FilterLinkEntityAny()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<filter type='or'>
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='any'>
<filter type='and'>
<condition attribute='name'
operator='eq'
value='Contoso' />
</filter>
</link-entity>
<condition attribute='statecode'
operator='eq'
value='1' />
</filter>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT fullname FROM contact WHERE (EXISTS( SELECT account.primarycontactid FROM account WHERE account.name = 'Contoso' AND contact.contactid = account.primarycontactid ) OR statecode = '1')"), NormalizeWhitespace(converted));
}

[TestMethod]
public void FilterLinkEntityNotAny()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<filter type='and'>
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='not any'>
<filter type='and'>
<condition attribute='name'
operator='eq'
value='Contoso' />
</filter>
</link-entity>
</filter>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT fullname FROM contact WHERE NOT EXISTS( SELECT account.primarycontactid FROM account WHERE account.name = 'Contoso' AND contact.contactid = account.primarycontactid )"), NormalizeWhitespace(converted));
}

[TestMethod]
public void FilterLinkEntityNotAll()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<filter type='and'>
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='not all'>
<filter type='and'>
<condition attribute='name'
operator='eq'
value='Contoso' />
</filter>
</link-entity>
</filter>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT fullname FROM contact WHERE EXISTS( SELECT account.primarycontactid FROM account WHERE account.name = 'Contoso' AND contact.contactid = account.primarycontactid )"), NormalizeWhitespace(converted));
}

[TestMethod]
public void FilterLinkEntityAll()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
<fetch>
<entity name='contact'>
<attribute name='fullname' />
<filter type='and'>
<link-entity name='account'
from='primarycontactid'
to='contactid'
link-type='all'>
<filter type='and'>
<condition attribute='name'
operator='eq'
value='Contoso' />
</filter>
</link-entity>
</filter>
</entity>
</fetch>";

var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions { ConvertFetchXmlOperatorsTo = FetchXmlOperatorConversion.SqlCalculations }, out _);

Assert.AreEqual(NormalizeWhitespace(@"
SELECT fullname FROM contact WHERE EXISTS( SELECT account.primarycontactid FROM account WHERE contact.contactid = account.primarycontactid ) AND NOT EXISTS( SELECT account.primarycontactid FROM account WHERE account.name = 'Contoso' AND contact.contactid = account.primarycontactid )"), NormalizeWhitespace(converted));
}

private static string NormalizeWhitespace(string s)
{
return Regex.Replace(s, "\\s+", " ").Trim();
Expand Down
13 changes: 13 additions & 0 deletions MarkMpn.Sql4Cds.Engine.FetchXml.Tests/Metadata/New_CustomEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,18 @@ class New_CustomEntity

[RelationshipSchemaName("new_customentity_children")]
public IEnumerable<New_CustomEntity> Children { get; }

[AttributeLogicalName("new_optionsetvalue")]
public New_OptionSet? New_OptionSetValue { get; set; }

[AttributeLogicalName("new_optionsetvaluename")]
public string New_OptionSetValueName { get; set; }
}

enum New_OptionSet
{
Value1 = 100001,
Value2,
Value3
}
}
Loading

0 comments on commit 4a86ce7

Please sign in to comment.