diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
index 4c98f0122..197dc166e 100644
--- a/.github/workflows/config.yml
+++ b/.github/workflows/config.yml
@@ -3,7 +3,7 @@ name: SingleStore EntityFramework Core
on: [push]
env:
- DOTNET_VERSION: 7.0.404
+ DOTNET_VERSION: 8.0.405
LICENSE_KEY: ${{ secrets.LICENSE_KEY }}
SQL_USER_PASSWORD: ${{ secrets.SQL_USER_PASSWORD }}
S2MS_API_KEY: ${{ secrets.S2MS_API_KEY }}
@@ -15,7 +15,6 @@ jobs:
fail-fast: false
matrix:
singlestore_image:
- - singlestore/cluster-in-a-box:alma-8.1.30-e0a67e68e5-4.0.16-1.17.6
- singlestore/cluster-in-a-box:alma-8.5.6-b51bc5471a-4.0.17-1.17.8
- singlestore/cluster-in-a-box:alma-8.7.12-483e5f8acb-4.1.0-1.17.15
func_test_script:
@@ -75,13 +74,13 @@ jobs:
run: dotnet build SingleStore.EFCore.sln -c Release
- name: Run Unit Tests
- run: dotnet test test/EFCore.SingleStore.Tests -f net7.0 -c Release --no-build
+ run: dotnet test test/EFCore.SingleStore.Tests -f net8.0 -c Release --no-build
- name: Rebuild migrations
run: pwsh ./test/EFCore.SingleStore.IntegrationTests/scripts/rebuild.ps1
- name: Run Integration Tests
- run: dotnet test test/EFCore.SingleStore.IntegrationTests -f net7.0 -c Release --no-build
+ run: dotnet test test/EFCore.SingleStore.IntegrationTests -f net8.0 -c Release --no-build
- name: Run Functional Tests ${{ matrix.singlestore_image }} - ${{ matrix.func_test_script }}
run: ${{ matrix.func_test_script }}
@@ -121,13 +120,13 @@ jobs:
run: dotnet build SingleStore.EFCore.sln -c Release
- name: Run EFCore.SingleStore.Tests
- run: .\.github\workflows\test_setup\run-test-windows.ps1 -test_block EFCore.SingleStore.Tests -target_framework net7.0
+ run: .\.github\workflows\test_setup\run-test-windows.ps1 -test_block EFCore.SingleStore.Tests -target_framework net8.0
- name: Rebuild migrations
run: .\test\EFCore.SingleStore.IntegrationTests\scripts\rebuild.ps1
- name: Run EFCore.SingleStore.IntegrationTests
- run: .\.github\workflows\test_setup\run-test-windows.ps1 -test_block EFCore.SingleStore.IntegrationTests -target_framework net7.0
+ run: .\.github\workflows\test_setup\run-test-windows.ps1 -test_block EFCore.SingleStore.IntegrationTests -target_framework net8.0
- name: Run Functional Test Block
run: ${{ matrix.func_test_block_path }}
diff --git a/.github/workflows/test_setup/config.json b/.github/workflows/test_setup/config.json
index 965781fff..e1fc6d4f2 100644
--- a/.github/workflows/test_setup/config.json
+++ b/.github/workflows/test_setup/config.json
@@ -1,6 +1,6 @@
{
"Data": {
- "ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest",
+ "ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest;sslmode=None",
"ServerVersion": "auto",
"CommandTimeout": "600"
}
diff --git a/.github/workflows/test_setup/run-functional-tests1.ps1 b/.github/workflows/test_setup/run-functional-tests1.ps1
index 6f4022507..712939ef1 100644
--- a/.github/workflows/test_setup/run-functional-tests1.ps1
+++ b/.github/workflows/test_setup/run-functional-tests1.ps1
@@ -1,124 +1,124 @@
cd test\EFCore.SingleStore.FunctionalTests\
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsQuerySingleStoreTest.'
$TOTAL_FAILURES = ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.Ef6GroupBySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.Ef6GroupBySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysSplitQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysSplitQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DateOnlyQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DateOnlyQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.EscapesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.EscapesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldsOnlyLoadSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldsOnlyLoadSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlSprocQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlSprocQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FunkyDataQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FunkyDataQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarFromSqlQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarFromSqlQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.IncludeOneToOneSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.IncludeOneToOneSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoChangeTrackingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoChangeTrackingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.BoolIndexingOptimizationDisabledSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.BoolIndexingOptimizationDisabledSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomChangeTrackingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomChangeTrackingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomQueryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomQueryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceRelationshipsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceRelationshipsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSharedTypeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSharedTypeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceQuerySingleStoreTest.'
$TOTAL_FAILURES = ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTGearsOfWarQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTGearsOfWarQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTRelationshipsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTRelationshipsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.WarningsSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.WarningsSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorDisabledSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorDisabledSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeyEndToEndSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeyEndToEndSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CommandInterceptionSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CommandInterceptionSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.BuiltInDataTypesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.BuiltInDataTypesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSettingsSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSettingsSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~ConnectionInterceptionSingleStoreTest'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~ConnectionInterceptionSingleStoreTest'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConferencePlannerSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConferencePlannerSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorEnabledSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorEnabledSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DataAnnotationSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DataAnnotationSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CustomConvertersSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CustomConvertersSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConvertToProviderTypesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConvertToProviderTypesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DesignTimeSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DesignTimeSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DefaultValuesTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DefaultValuesTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DatabindingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DatabindingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~FindSingleStoreTest'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~FindSingleStoreTest'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldMappingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldMappingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ExistingConnectionSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ExistingConnectionSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreTestBase.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreTestBase.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientNoActionTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientNoActionTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientCascadeTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientCascadeTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FullInfrastructureMigrationsTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FullInfrastructureMigrationsTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.LoggingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.LoggingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.LoadSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.LoadSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.LazyLoadProxySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.LazyLoadProxySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.KeysWithConvertersSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.KeysWithConvertersSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyLoadSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyLoadSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyTrackingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyTrackingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MaterializationInterceptionSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MaterializationInterceptionSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
diff --git a/.github/workflows/test_setup/run-functional-tests2.ps1 b/.github/workflows/test_setup/run-functional-tests2.ps1
index 14200d659..ac9cf1cc3 100644
--- a/.github/workflows/test_setup/run-functional-tests2.ps1
+++ b/.github/workflows/test_setup/run-functional-tests2.ps1
@@ -1,122 +1,122 @@
cd test\EFCore.SingleStore.FunctionalTests\
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitSharedTypeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitSharedTypeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsSharedTypeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsSharedTypeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoQueryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoQueryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringChangeTrackingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringChangeTrackingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringQueryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringQueryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomChangeTrackingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomChangeTrackingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomQueryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomQueryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoChangeTrackingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoChangeTrackingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoQueryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoQueryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringChangeTrackingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringChangeTrackingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringQueryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringQueryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyHeterogeneousQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyHeterogeneousQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MappingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MappingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MatchQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MatchQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsTrackingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsTrackingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsNoTrackingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsNoTrackingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateOperatorsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateOperatorsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindDbFunctionsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindDbFunctionsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindCompiledQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindCompiledQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindChangeTrackingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindChangeTrackingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindGroupByQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindGroupByQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindFunctionsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindFunctionsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindEFPropertyIncludeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindEFPropertyIncludeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindKeylessEntitiesQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindKeylessEntitiesQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindJoinQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindJoinQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeNoTrackingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeNoTrackingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryFiltersQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryFiltersQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindMiscellaneousQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindMiscellaneousQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSelectQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSelectQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSetOperationsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSetOperationsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeNoTrackingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeNoTrackingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSqlQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSqlQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindStringIncludeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindStringIncludeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindWhereQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindWhereQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NullKeysSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NullKeysSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NullSemanticsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NullSemanticsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedEntityQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedEntityQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryFilterFuncletizationSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryFilterFuncletizationSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindNavigationsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindNavigationsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SharedTypeQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SharedTypeQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryNoClientEvalSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryNoClientEvalSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SimpleQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SimpleQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SqlExecutorSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SqlExecutorSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ToSqlQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ToSqlQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCGearsOfWarQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCGearsOfWarQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyNoTrackingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyNoTrackingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCRelationshipsQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCRelationshipsQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
cd ..\..\
diff --git a/.github/workflows/test_setup/run-functional-tests3.ps1 b/.github/workflows/test_setup/run-functional-tests3.ps1
index db2d3f03b..e46f3c35c 100644
--- a/.github/workflows/test_setup/run-functional-tests3.ps1
+++ b/.github/workflows/test_setup/run-functional-tests3.ps1
@@ -1,88 +1,78 @@
cd test\EFCore.SingleStore.FunctionalTests\
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsInfrastructureSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsInfrastructureSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OptimisticConcurrencySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OptimisticConcurrencySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NotificationEntitiesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NotificationEntitiesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryTaggingQuerySingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryTaggingQuerySingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MusicStoreSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MusicStoreSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.PropertyValuesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.PropertyValuesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OverzealousInitializationSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OverzealousInitializationSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreDatabaseModelFactoryTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreDatabaseModelFactoryTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreValueGenerationSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreValueGenerationSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreTypeMappingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreTypeMappingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoredProcedureUpdateSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoredProcedureUpdateSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonMicrosoftTypeMappingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonMicrosoftTypeMappingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonNewtonsoftTypeMappingTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonNewtonsoftTypeMappingTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreUpdateSqlGeneratorTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreUpdateSqlGeneratorTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~SaveChangesInterceptionSingleStoreTest'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~SaveChangesInterceptionSingleStoreTest'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SeedingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SeedingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SerializationSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SerializationSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreMigrationsSqlGeneratorTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreMigrationsSqlGeneratorTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreComplianceTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreComplianceTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreApiConsistencyTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreApiConsistencyTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedFixupSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedFixupSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SpatialSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SpatialSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreServiceCollectionExtensionsTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreServiceCollectionExtensionsTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreNetTopologySuiteApiConsistencyTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreNetTopologySuiteApiConsistencyTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TableSplittingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TableSplittingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTTableSplittingSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTTableSplittingSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionInterceptionSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionInterceptionSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TwoDatabasesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TwoDatabasesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.UpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.UpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ValueConvertersEndToEndSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ValueConvertersEndToEndSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.WithConstructorsSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.WithConstructorsSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelBulkUpdatesSingleStoreTest.'
-$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindBulkUpdatesSingleStoreTest.'
-$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceBulkUpdatesSingleStoreTest.'
-$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceBulkUpdatesSingleStoreTest.'
-$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceBulkUpdatesSingleStoreTest.'
-$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
-dotnet.exe test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceBulkUpdatesSingleStoreTest.'
+dotnet.exe test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelBulkUpdatesSingleStoreTest.'
$TOTAL_FAILURES += ($LASTEXITCODE -ne 0)
cd ..\..\
diff --git a/.github/workflows/test_setup/run_functional_tests1.sh b/.github/workflows/test_setup/run_functional_tests1.sh
index e625f5002..f4e5b9c34 100755
--- a/.github/workflows/test_setup/run_functional_tests1.sh
+++ b/.github/workflows/test_setup/run_functional_tests1.sh
@@ -1,168 +1,168 @@
cd test/EFCore.SingleStore.FunctionalTests/
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TwoDatabasesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TwoDatabasesSingleStoreTest.'
((TOTAL_FAILURES = $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.Ef6GroupBySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.Ef6GroupBySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysSplitQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeysSplitQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DateOnlyQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DateOnlyQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.EscapesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.EscapesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldsOnlyLoadSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldsOnlyLoadSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlSprocQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FromSqlSprocQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FunkyDataQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FunkyDataQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarFromSqlQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarFromSqlQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GearsOfWarQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.IncludeOneToOneSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.IncludeOneToOneSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoChangeTrackingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoChangeTrackingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.BoolIndexingOptimizationDisabledSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.BoolIndexingOptimizationDisabledSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomChangeTrackingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomChangeTrackingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomQueryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftDomQueryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceRelationshipsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceRelationshipsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSharedTypeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSharedTypeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitSharedTypeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsCollectionsSplitSharedTypeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsSharedTypeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ComplexNavigationsSharedTypeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoQueryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftPocoQueryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringChangeTrackingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringChangeTrackingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringQueryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonMicrosoftStringQueryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomChangeTrackingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomChangeTrackingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomQueryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftDomQueryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoChangeTrackingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoChangeTrackingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoQueryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftPocoQueryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringChangeTrackingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringChangeTrackingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringQueryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.JsonNewtonsoftStringQueryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyHeterogeneousQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyHeterogeneousQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MappingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MappingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MatchQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MatchQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsTrackingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsTrackingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsNoTrackingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAsNoTrackingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateOperatorsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindAggregateOperatorsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindDbFunctionsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindDbFunctionsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindCompiledQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindCompiledQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindChangeTrackingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindChangeTrackingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindGroupByQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindGroupByQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindFunctionsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindFunctionsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindEFPropertyIncludeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindEFPropertyIncludeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindKeylessEntitiesQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindKeylessEntitiesQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindJoinQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindJoinQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeNoTrackingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindIncludeNoTrackingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryFiltersQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryFiltersQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindMiscellaneousQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindMiscellaneousQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSelectQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSelectQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSetOperationsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSetOperationsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeNoTrackingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeNoTrackingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSplitIncludeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSqlQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindSqlQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindStringIncludeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindStringIncludeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindWhereQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindWhereQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NullKeysSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NullKeysSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NullSemanticsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NullSemanticsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedEntityQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedEntityQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OwnedQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryFilterFuncletizationSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryFilterFuncletizationSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindNavigationsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindNavigationsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SharedTypeQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SharedTypeQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryNoClientEvalSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.QueryNoClientEvalSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SimpleQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SimpleQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SqlExecutorSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SqlExecutorSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ToSqlQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ToSqlQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.WithConstructorsSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.WithConstructorsSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FiltersInheritanceBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.InheritanceBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceBulkUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceBulkUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
cd ../../
diff --git a/.github/workflows/test_setup/run_functional_tests2.sh b/.github/workflows/test_setup/run_functional_tests2.sh
index 1ed38cfde..e1135ee75 100755
--- a/.github/workflows/test_setup/run_functional_tests2.sh
+++ b/.github/workflows/test_setup/run_functional_tests2.sh
@@ -1,152 +1,152 @@
cd test/EFCore.SingleStore.FunctionalTests/
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FullInfrastructureMigrationsTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FullInfrastructureMigrationsTest.'
((TOTAL_FAILURES = $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCGearsOfWarQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCGearsOfWarQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCFiltersInheritanceQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCInheritanceQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyNoTrackingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyNoTrackingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCManyToManyQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCRelationshipsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPCRelationshipsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTFiltersInheritanceQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTGearsOfWarQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTGearsOfWarQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTInheritanceQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTRelationshipsQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTRelationshipsQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.WarningsSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.WarningsSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorDisabledSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorDisabledSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeyEndToEndSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CompositeKeyEndToEndSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CommandInterceptionSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CommandInterceptionSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.BuiltInDataTypesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.BuiltInDataTypesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSettingsSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSettingsSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~ConnectionInterceptionSingleStoreTest'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~ConnectionInterceptionSingleStoreTest'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConferencePlannerSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConferencePlannerSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorEnabledSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConcurrencyDetectorEnabledSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DataAnnotationSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DataAnnotationSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.CustomConvertersSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.CustomConvertersSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConvertToProviderTypesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConvertToProviderTypesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ConnectionSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.EntitySplittingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelUpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NonSharedModelUpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DesignTimeSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DesignTimeSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DefaultValuesTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DefaultValuesTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.DatabindingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.DatabindingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~FindSingleStoreTest'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~FindSingleStoreTest'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldMappingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.FieldMappingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ExistingConnectionSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ExistingConnectionSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreTestBase.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreTestBase.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientNoActionTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientNoActionTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientCascadeTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.GraphUpdatesSingleStoreClientCascadeTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.LoggingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.LoggingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.LoadSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.LoadSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.LazyLoadProxySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.LazyLoadProxySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.KeysWithConvertersSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.KeysWithConvertersSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyLoadSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyLoadSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyTrackingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ManyToManyTrackingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MaterializationInterceptionSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MaterializationInterceptionSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsInfrastructureSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MigrationsInfrastructureSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OptimisticConcurrencySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OptimisticConcurrencySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NotificationEntitiesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NotificationEntitiesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryTaggingQuerySingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.NorthwindQueryTaggingQuerySingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.MusicStoreSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.MusicStoreSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.PropertyValuesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.PropertyValuesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.OverzealousInitializationSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.OverzealousInitializationSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreDatabaseModelFactoryTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreDatabaseModelFactoryTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreValueGenerationSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreValueGenerationSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreTypeMappingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreTypeMappingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoredProcedureUpdateSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoredProcedureUpdateSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonMicrosoftTypeMappingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonMicrosoftTypeMappingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonNewtonsoftTypeMappingTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreJsonNewtonsoftTypeMappingTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreUpdateSqlGeneratorTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreUpdateSqlGeneratorTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~SaveChangesInterceptionSingleStoreTest'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~SaveChangesInterceptionSingleStoreTest'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SeedingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SeedingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SerializationSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SerializationSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreMigrationsSqlGeneratorTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreMigrationsSqlGeneratorTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreComplianceTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreComplianceTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreApiConsistencyTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreApiConsistencyTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedFixupSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.StoreGeneratedFixupSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SpatialSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SpatialSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreServiceCollectionExtensionsTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreServiceCollectionExtensionsTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreNetTopologySuiteApiConsistencyTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.SingleStoreNetTopologySuiteApiConsistencyTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TableSplittingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TableSplittingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTTableSplittingSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TPTTableSplittingSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionInterceptionSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionInterceptionSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.TransactionSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.ValueConvertersEndToEndSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.ValueConvertersEndToEndSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
-dotnet test -f net7.0 -c Release --no-build --filter 'FullyQualifiedName~.UpdatesSingleStoreTest.'
+dotnet test -f net8.0 -c Release --no-build --filter 'FullyQualifiedName~.UpdatesSingleStoreTest.'
((TOTAL_FAILURES += $? != 0))
cd ../../
diff --git a/.github/workflows/test_setup/s2ms_cluster.py b/.github/workflows/test_setup/s2ms_cluster.py
index 9cbc9c2cd..88320c6e0 100644
--- a/.github/workflows/test_setup/s2ms_cluster.py
+++ b/.github/workflows/test_setup/s2ms_cluster.py
@@ -10,7 +10,7 @@
WORKSPACE_GROUP_BASE_NAME = "ef-core-provider-ci-test-cluster"
WORKSPACE_NAME = "tests"
-AWS_US_WEST_REGION = "1c1de314-2cc0-4c74-bd54-5047ff90842e"
+
AUTO_TERMINATE_MINUTES = 150
WORKSPACE_ENDPOINT_FILE = "WORKSPACE_ENDPOINT_FILE"
WORKSPACE_GROUP_ID_FILE = "WORKSPACE_GROUP_ID_FILE"
@@ -28,11 +28,16 @@ def retry(func):
def create_workspace(workspace_manager):
+ for reg in workspace_manager.regions:
+ if 'US' in reg.name:
+ region = reg
+ break
+
w_group_name = WORKSPACE_GROUP_BASE_NAME + "-" + uuid.uuid4().hex
def create_workspace_group():
return workspace_manager.create_workspace_group(
name=w_group_name,
- region=AWS_US_WEST_REGION,
+ region=region.id,
firewall_ranges=["0.0.0.0/0"],
admin_password=SQL_USER_PASSWORD,
expires_at="150m"
diff --git a/Dependencies.targets b/Dependencies.targets
index ca97b0212..110ba5897 100644
--- a/Dependencies.targets
+++ b/Dependencies.targets
@@ -1,6 +1,6 @@
- 7.*
+ [8.0.0,8.0.999]
@@ -9,40 +9,44 @@
-
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
-
-
-
+
-
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 9866f2f48..7984d8a57 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,7 +12,6 @@
singlestore_logo.png
true
latest
- portable
true
MIT
https://github.com/memsql/SingleStore.EntityFrameworkCore
@@ -21,10 +20,15 @@
- net6.0;net7.0
- net7.0
- net6.0
- net7.0
+ portable
+
+
+
+ net8.0
+ net8.0
+ net8.0
+ net8.0
+ net8.0
diff --git a/README.md b/README.md
index 2e126b676..3eff634d7 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
Milestone | Status | Release Date
----------|----------------------|-------------
+8.0.0| released | July 2025
7.0.1| released | March 2025
7.0.0| released | November 2024
6.0.2| general availability | April 2024
@@ -16,7 +17,7 @@ Milestone | Status | Release Date
Ensure that your `.csproj` file contains the following reference:
```xml
-
+
```
### 2. Services Configuration
@@ -45,7 +46,7 @@ public class Startup
}
```
-View our [Configuration Options Wiki Page](https://github.com/PomeloFoundation/EntityFrameworkCore.MySql/wiki/Configuration-Options) for a list of common options.
+View our [Configuration Options Wiki Page](https://github.com/memsql/SingleStore.EntityFrameworkCore/wiki/Configuration-Options) for a list of common options.
### 3. Sample Application
diff --git a/Version.props b/Version.props
index c8aee9ac8..9ba606ba6 100644
--- a/Version.props
+++ b/Version.props
@@ -12,7 +12,7 @@
- "rtm" - EF Core release independent, code quality production ready, major release
- "servicing" - EF Core release independent, code quality production ready, mainly bugfixes
-->
- 7.0.1
+ 8.0.0
rtm
@@ -73,4 +68,8 @@
+
+
+
+
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Query/Internal/SingleStoreJsonMicrosoftDomTranslator.cs b/src/EFCore.SingleStore.Json.Microsoft/Query/Internal/SingleStoreJsonMicrosoftDomTranslator.cs
index 9a3140041..67d76856d 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Query/Internal/SingleStoreJsonMicrosoftDomTranslator.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Query/Internal/SingleStoreJsonMicrosoftDomTranslator.cs
@@ -186,7 +186,7 @@ private SqlExpression ApplyPathLocationTypeMapping(SqlExpression expression)
sqlConstantExpression.TypeMapping is SingleStoreStringTypeMapping stringTypeMapping &&
!stringTypeMapping.IsUnquoted)
{
- pathLocation = sqlConstantExpression.ApplyTypeMapping(stringTypeMapping.Clone(true));
+ pathLocation = sqlConstantExpression.ApplyTypeMapping(stringTypeMapping.Clone(unquoted: true));
}
return pathLocation;
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMapping.cs b/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMapping.cs
index 3caddc3e5..dd3aca1fe 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMapping.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMapping.cs
@@ -10,38 +10,53 @@
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SingleStoreConnector;
-using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Storage.Internal;
namespace EntityFrameworkCore.SingleStore.Json.Microsoft.Storage.Internal
{
public class SingleStoreJsonMicrosoftTypeMapping : SingleStoreJsonTypeMapping
{
+ public static new SingleStoreJsonMicrosoftTypeMapping Default { get; } = new("json", null, null, false, true);
+
// Called via reflection.
// ReSharper disable once UnusedMember.Global
public SingleStoreJsonMicrosoftTypeMapping(
[NotNull] string storeType,
[CanBeNull] ValueConverter valueConverter,
[CanBeNull] ValueComparer valueComparer,
- [NotNull] ISingleStoreOptions options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
: base(
storeType,
valueConverter,
valueComparer,
- options)
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction)
{
}
protected SingleStoreJsonMicrosoftTypeMapping(
RelationalTypeMappingParameters parameters,
SingleStoreDbType mySqlDbType,
- ISingleStoreOptions options)
- : base(parameters, mySqlDbType, options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
+ : base(
+ parameters,
+ mySqlDbType,
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction)
{
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new SingleStoreJsonMicrosoftTypeMapping(parameters, SingleStoreDbType, Options);
+ => new SingleStoreJsonMicrosoftTypeMapping(parameters, SingleStoreDbType, NoBackslashEscapes, ReplaceLineBreaksWithCharFunction);
+
+ protected override RelationalTypeMapping Clone(bool? noBackslashEscapes = null, bool? replaceLineBreaksWithCharFunction = null)
+ => new SingleStoreJsonMicrosoftTypeMapping(
+ Parameters,
+ SingleStoreDbType,
+ noBackslashEscapes ?? NoBackslashEscapes,
+ replaceLineBreaksWithCharFunction ?? ReplaceLineBreaksWithCharFunction);
public override Expression GenerateCodeLiteral(object value)
=> value switch
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMappingSourcePlugin.cs b/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMappingSourcePlugin.cs
index d0ca382f9..19c505562 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMappingSourcePlugin.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Storage/Internal/SingleStoreJsonMicrosoftTypeMappingSourcePlugin.cs
@@ -41,7 +41,8 @@ protected override RelationalTypeMapping FindDomMapping(RelationalTypeMappingInf
"json",
GetValueConverter(clrType),
GetValueComparer(clrType),
- Options);
+ Options.NoBackslashEscapes,
+ Options.ReplaceLineBreaksWithCharFunction);
}
return null;
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonDocumentValueConverter.cs b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonDocumentValueConverter.cs
index 59a85445d..1442a7197 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonDocumentValueConverter.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonDocumentValueConverter.cs
@@ -19,7 +19,7 @@ public SingleStoreJsonMicrosoftJsonDocumentValueConverter()
{
}
- private static string ConvertToProviderCore(JsonDocument v)
+ public static string ConvertToProviderCore(JsonDocument v)
{
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
@@ -28,7 +28,7 @@ private static string ConvertToProviderCore(JsonDocument v)
return Encoding.UTF8.GetString(stream.ToArray());
}
- private static JsonDocument ConvertFromProviderCore(string v)
+ public static JsonDocument ConvertFromProviderCore(string v)
=> JsonDocument.Parse(v);
}
}
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonElementValueConverter.cs b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonElementValueConverter.cs
index 993441ae1..ec697b5c8 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonElementValueConverter.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftJsonElementValueConverter.cs
@@ -15,7 +15,7 @@ public SingleStoreJsonMicrosoftJsonElementValueConverter()
{
}
- private static string ConvertToProviderCore(JsonElement v)
+ public static string ConvertToProviderCore(JsonElement v)
{
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
@@ -24,7 +24,7 @@ private static string ConvertToProviderCore(JsonElement v)
return Encoding.UTF8.GetString(stream.ToArray());
}
- private static JsonElement ConvertFromProviderCore(string v)
+ public static JsonElement ConvertFromProviderCore(string v)
=> JsonDocument.Parse(v).RootElement;
}
}
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftPocoValueConverter.cs b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftPocoValueConverter.cs
index 841e86ecd..bc1b31174 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftPocoValueConverter.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftPocoValueConverter.cs
@@ -17,10 +17,10 @@ public SingleStoreJsonMicrosoftPocoValueConverter()
{
}
- private static string ConvertToProviderCore(T v)
+ public static string ConvertToProviderCore(T v)
=> JsonSerializer.Serialize(v);
- private static T ConvertFromProviderCore(string v)
+ public static T ConvertFromProviderCore(string v)
=> JsonSerializer.Deserialize(v);
}
}
diff --git a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftStringValueConverter.cs b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftStringValueConverter.cs
index e2d508d0f..c51649d3a 100644
--- a/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftStringValueConverter.cs
+++ b/src/EFCore.SingleStore.Json.Microsoft/Storage/ValueConversion/Internal/SingleStoreJsonMicrosoftStringValueConverter.cs
@@ -19,10 +19,10 @@ public SingleStoreJsonMicrosoftStringValueConverter()
{
}
- private static string ConvertToProviderCore(string v)
+ public static string ConvertToProviderCore(string v)
=> ProcessJsonString(v);
- private static string ConvertFromProviderCore(string v)
+ public static string ConvertFromProviderCore(string v)
=> ProcessJsonString(v);
internal static string ProcessJsonString(string v)
diff --git a/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj b/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj
index 1126bf382..95b867016 100644
--- a/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj
+++ b/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj
@@ -2,8 +2,7 @@
JSON support using Newtonsoft.Json (JSON.NET) for SingleStore provider for Entity Framework Core.
- $(SingleStoreTargetFrameworks)
- 3.6
+ $(SingleStoreTargetFramework)
EntityFrameworkCore.SingleStore.Json.Newtonsoft
EntityFrameworkCore.SingleStore.Json.Newtonsoft
true
@@ -34,7 +33,7 @@
-
+
@@ -45,13 +44,6 @@
-
-
-
- $(LocalSingleStoreConnectorRepository)\src\SingleStoreConnector\bin\Debug\$(TargetFramework)\SingleStoreConnector.dll
-
-
-
diff --git a/src/EFCore.SingleStore.Json.Newtonsoft/Query/Internal/SingleStoreJsonNewtonsoftDomTranslator.cs b/src/EFCore.SingleStore.Json.Newtonsoft/Query/Internal/SingleStoreJsonNewtonsoftDomTranslator.cs
index 351eb8978..14bb923ed 100644
--- a/src/EFCore.SingleStore.Json.Newtonsoft/Query/Internal/SingleStoreJsonNewtonsoftDomTranslator.cs
+++ b/src/EFCore.SingleStore.Json.Newtonsoft/Query/Internal/SingleStoreJsonNewtonsoftDomTranslator.cs
@@ -182,7 +182,7 @@ private SqlExpression ApplyPathLocationTypeMapping(SqlExpression expression)
sqlConstantExpression.TypeMapping is SingleStoreStringTypeMapping stringTypeMapping &&
!stringTypeMapping.IsUnquoted)
{
- pathLocation = sqlConstantExpression.ApplyTypeMapping(stringTypeMapping.Clone(true));
+ pathLocation = sqlConstantExpression.ApplyTypeMapping(stringTypeMapping.Clone(unquoted: true));
}
return pathLocation;
diff --git a/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMapping.cs b/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMapping.cs
index bdd5e84d1..4c3ff3f37 100644
--- a/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMapping.cs
+++ b/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMapping.cs
@@ -4,7 +4,6 @@
using System;
using System.Linq.Expressions;
-using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
@@ -12,38 +11,49 @@
using SingleStoreConnector;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Storage.Internal;
namespace EntityFrameworkCore.SingleStore.Json.Newtonsoft.Storage.Internal
{
public class SingleStoreJsonNewtonsoftTypeMapping : SingleStoreJsonTypeMapping
{
+ public static new SingleStoreJsonNewtonsoftTypeMapping Default { get; } = new("json", null, null, false, true);
+
// Called via reflection.
// ReSharper disable once UnusedMember.Global
public SingleStoreJsonNewtonsoftTypeMapping(
[NotNull] string storeType,
[CanBeNull] ValueConverter valueConverter,
[CanBeNull] ValueComparer valueComparer,
- [NotNull] ISingleStoreOptions options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
: base(
storeType,
valueConverter,
valueComparer,
- options)
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction)
{
}
protected SingleStoreJsonNewtonsoftTypeMapping(
RelationalTypeMappingParameters parameters,
SingleStoreDbType mySqlDbType,
- ISingleStoreOptions options)
- : base(parameters, mySqlDbType, options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
+ : base(parameters, mySqlDbType, noBackslashEscapes, replaceLineBreaksWithCharFunction)
{
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new SingleStoreJsonNewtonsoftTypeMapping(parameters, SingleStoreDbType, Options);
+ => new SingleStoreJsonNewtonsoftTypeMapping(parameters, SingleStoreDbType, NoBackslashEscapes, ReplaceLineBreaksWithCharFunction);
+
+ protected override RelationalTypeMapping Clone(bool? noBackslashEscapes = null, bool? replaceLineBreaksWithCharFunction = null)
+ => new SingleStoreJsonNewtonsoftTypeMapping(
+ Parameters,
+ SingleStoreDbType,
+ noBackslashEscapes ?? NoBackslashEscapes,
+ replaceLineBreaksWithCharFunction ?? ReplaceLineBreaksWithCharFunction);
public override Expression GenerateCodeLiteral(object value)
=> value switch
diff --git a/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMappingSourcePlugin.cs b/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMappingSourcePlugin.cs
index 1047ad243..1798e4202 100644
--- a/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMappingSourcePlugin.cs
+++ b/src/EFCore.SingleStore.Json.Newtonsoft/Storage/Internal/SingleStoreJsonNewtonsoftTypeMappingSourcePlugin.cs
@@ -39,7 +39,8 @@ protected override RelationalTypeMapping FindDomMapping(RelationalTypeMappingInf
"json",
GetValueConverter(clrType),
GetValueComparer(clrType),
- Options);
+ Options.NoBackslashEscapes,
+ Options.ReplaceLineBreaksWithCharFunction);
}
return null;
diff --git a/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj b/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj
index 1b9040b3b..671c80b57 100644
--- a/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj
+++ b/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj
@@ -2,8 +2,7 @@
NetTopologySuite support for SingleStore provider for Entity Framework Core.
- $(SingleStoreTargetFrameworks)
- 3.6
+ $(SingleStoreTargetFramework)
EntityFrameworkCore.SingleStore.NetTopologySuite
EntityFrameworkCore.SingleStore
true
@@ -34,7 +33,7 @@
-
+
@@ -45,13 +44,6 @@
-
-
-
- $(LocalSingleStoreConnectorRepository)\src\SingleStoreConnector\bin\Debug\$(TargetFramework)\SingleStoreConnector.dll
-
-
-
diff --git a/src/EFCore.SingleStore/Design/Internal/SingleStoreCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.SingleStore/Design/Internal/SingleStoreCSharpRuntimeAnnotationCodeGenerator.cs
new file mode 100644
index 000000000..5f2a326fa
--- /dev/null
+++ b/src/EFCore.SingleStore/Design/Internal/SingleStoreCSharpRuntimeAnnotationCodeGenerator.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Design.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
+using EntityFrameworkCore.SingleStore.Storage.Internal;
+
+namespace EntityFrameworkCore.SingleStore.Design.Internal;
+
+public class SingleStoreCSharpRuntimeAnnotationCodeGenerator : RelationalCSharpRuntimeAnnotationCodeGenerator
+{
+ public SingleStoreCSharpRuntimeAnnotationCodeGenerator(
+ CSharpRuntimeAnnotationCodeGeneratorDependencies dependencies,
+ RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies relationalDependencies)
+ : base(dependencies, relationalDependencies)
+ {
+ }
+
+ public override bool Create(
+ CoreTypeMapping typeMapping,
+ CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
+ ValueComparer valueComparer = null,
+ ValueComparer keyValueComparer = null,
+ ValueComparer providerValueComparer = null)
+ {
+ var result = base.Create(typeMapping, parameters, valueComparer, keyValueComparer, providerValueComparer);
+
+ if (typeMapping is ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator extension)
+ {
+ extension.Create(parameters, Dependencies);
+ }
+
+ return result;
+ }
+}
diff --git a/src/EFCore.SingleStore/Design/Internal/SingleStoreDesignTimeServices.cs b/src/EFCore.SingleStore/Design/Internal/SingleStoreDesignTimeServices.cs
index 3b6e56121..f22cc3098 100644
--- a/src/EFCore.SingleStore/Design/Internal/SingleStoreDesignTimeServices.cs
+++ b/src/EFCore.SingleStore/Design/Internal/SingleStoreDesignTimeServices.cs
@@ -4,6 +4,7 @@
using EntityFrameworkCore.SingleStore.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.Design;
+using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.Extensions.DependencyInjection;
@@ -15,6 +16,7 @@ public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollec
{
serviceCollection.AddEntityFrameworkSingleStore();
new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection)
+ .TryAdd()
.TryAdd()
.TryAdd()
.TryAdd()
diff --git a/src/EFCore.SingleStore/EFCore.SingleStore.csproj b/src/EFCore.SingleStore/EFCore.SingleStore.csproj
index b14470998..3bf900f1b 100644
--- a/src/EFCore.SingleStore/EFCore.SingleStore.csproj
+++ b/src/EFCore.SingleStore/EFCore.SingleStore.csproj
@@ -2,8 +2,7 @@
SingleStore database provider for Entity Framework Core.
- $(SingleStoreTargetFrameworks)
- 3.6
+ $(SingleStoreTargetFramework)
EntityFrameworkCore.SingleStore
EntityFrameworkCore.SingleStore
README.md
@@ -51,7 +50,7 @@
-
+
diff --git a/src/EFCore.SingleStore/Extensions/SingleStoreDatabaseFacadeExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStoreDatabaseFacadeExtensions.cs
index fc57d88f1..1cfcd4fda 100644
--- a/src/EFCore.SingleStore/Extensions/SingleStoreDatabaseFacadeExtensions.cs
+++ b/src/EFCore.SingleStore/Extensions/SingleStoreDatabaseFacadeExtensions.cs
@@ -3,22 +3,26 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Data.Common;
using System.Reflection;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
+using EntityFrameworkCore.SingleStore.Storage.Internal;
//ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
///
- /// MySQL specific extension methods for .
+ /// SingleStore specific extension methods for .
///
public static class SingleStoreDatabaseFacadeExtensions
{
///
///
- /// Returns true if the database provider currently in use is the MySQL provider.
+ /// Returns true if the database provider currently in use is the SingleStore provider.
///
///
/// This method can only be used after the has been configured because
@@ -28,10 +32,38 @@ public static class SingleStoreDatabaseFacadeExtensions
///
///
/// The facade from .
- /// True if MySQL is being used; false otherwise.
+ /// True if SingleStore is being used; false otherwise.
public static bool IsSingleStore([NotNull] this DatabaseFacade database)
=> database.ProviderName.Equals(
typeof(SingleStoreOptionsExtension).GetTypeInfo().Assembly.GetName().Name,
StringComparison.Ordinal);
+
+ ///
+ /// Uses a for this as the underlying database provider.
+ ///
+ ///
+ ///
+ /// It may not be possible to change the data source if an existing connection is open.
+ ///
+ ///
+ /// See Connections and connection strings for more information and examples.
+ ///
+ ///
+ /// The for the context.
+ /// The data source.
+ public static void SetDbDataSource(this DatabaseFacade databaseFacade, DbDataSource dataSource)
+ => ((SingleStoreRelationalConnection)GetFacadeDependencies(databaseFacade).RelationalConnection).DbDataSource = dataSource;
+
+ private static IRelationalDatabaseFacadeDependencies GetFacadeDependencies(DatabaseFacade databaseFacade)
+ {
+ var dependencies = ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Dependencies;
+
+ if (dependencies is IRelationalDatabaseFacadeDependencies relationalDependencies)
+ {
+ return relationalDependencies;
+ }
+
+ throw new InvalidOperationException(RelationalStrings.RelationalNotInUse);
+ }
}
}
diff --git a/src/EFCore.SingleStore/Extensions/SingleStoreDbContextOptionsBuilderExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStoreDbContextOptionsBuilderExtensions.cs
index 2c40d8343..654ec91dd 100644
--- a/src/EFCore.SingleStore/Extensions/SingleStoreDbContextOptionsBuilderExtensions.cs
+++ b/src/EFCore.SingleStore/Extensions/SingleStoreDbContextOptionsBuilderExtensions.cs
@@ -49,8 +49,10 @@ public static DbContextOptionsBuilder UseSingleStore(
optionsBuilder.AddInterceptors(new MatchInterceptor());
ServerVersion serverVersion = SingleStoreServerVersion.LatestSupportedServerVersion;
+
var extension = (SingleStoreOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithServerVersion(serverVersion);
+
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
ConfigureWarnings(optionsBuilder);
mySqlOptionsAction?.Invoke(new SingleStoreDbContextOptionsBuilder(optionsBuilder));
@@ -75,22 +77,6 @@ public static DbContextOptionsBuilder UseSingleStore(
optionsBuilder.AddInterceptors(new MatchInterceptor());
- if (connectionString is not null)
- {
- var resolvedConnectionString = new NamedConnectionStringResolver(optionsBuilder.Options)
- .ResolveConnectionString(connectionString);
-
- var programVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
- var csb = new SingleStoreConnectionStringBuilder(resolvedConnectionString)
- {
- AllowUserVariables = true,
- UseAffectedRows = false,
- ConnectionAttributes = $"_connector_name:SingleStore Entity Framework Core provider,_connector_version:{programVersion}",
- };
-
- connectionString = csb.ConnectionString;
- }
-
ServerVersion serverVersion;
try
@@ -137,37 +123,55 @@ public static DbContextOptionsBuilder UseSingleStore(
Check.NotNull(connection, nameof(connection));
optionsBuilder.AddInterceptors(new MatchInterceptor());
- var resolvedConnectionString = connection.ConnectionString is not null
- ? new NamedConnectionStringResolver(optionsBuilder.Options)
- .ResolveConnectionString(connection.ConnectionString)
- : null;
-
- var csb = new SingleStoreConnectionStringBuilder(resolvedConnectionString);
- var programVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
- csb.ConnectionAttributes = $"_connector_name:SingleStore Entity Framework Core provider,_connector_version:{programVersion}";
+ ServerVersion serverVersion;
- if (!csb.AllowUserVariables ||
- csb.UseAffectedRows)
+ try
{
- try
- {
- csb.AllowUserVariables = true;
- csb.UseAffectedRows = false;
-
- connection.ConnectionString = csb.ConnectionString;
- }
- catch (Exception e)
- {
- throw new InvalidOperationException(
- @"The connection string of a connection used by EntityFrameworkCore.SingleStore must contain ""AllowUserVariables=true;UseAffectedRows=false"".",
- e);
- }
+ serverVersion = ServerVersion.AutoDetect(connection.ConnectionString);
+ }
+ // There might occur different types of Exceptions while trying to AutoDetect() server version.
+ // This includes: SocketException (when we're unable to connect to any of the specified hosts),
+ // InvalidOperationException (unable to determine server version from version string), etc.
+ // In this case the latest supported server version will be used, therefore we catch any Exception.
+ catch (Exception)
+ {
+ serverVersion = SingleStoreServerVersion.LatestSupportedServerVersion;
}
+ var extension = (SingleStoreOptionsExtension)GetOrCreateExtension(optionsBuilder)
+ .WithServerVersion(serverVersion)
+ .WithConnection(connection);
+
+ ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
+ ConfigureWarnings(optionsBuilder);
+ mySqlOptionsAction?.Invoke(new SingleStoreDbContextOptionsBuilder(optionsBuilder));
+
+ return optionsBuilder;
+ }
+
+ ///
+ /// Configures the context to connect to a MySQL compatible database.
+ ///
+ /// The builder being used to configure the context.
+ /// A which will be used to get database connections.
+ /// An optional action to allow additional MySQL specific configuration.
+ /// The options builder so that further configuration can be chained.
+ public static DbContextOptionsBuilder UseSingleStore(
+ this DbContextOptionsBuilder optionsBuilder,
+ DbDataSource dataSource,
+ Action mySqlOptionsAction = null)
+ {
+ Check.NotNull(optionsBuilder, nameof(optionsBuilder));
+ Check.NotNull(dataSource, nameof(dataSource));
+
+ optionsBuilder.AddInterceptors(new MatchInterceptor());
ServerVersion serverVersion;
try
{
+ using var connection = dataSource.CreateConnection();
+ connection.Open();
+
serverVersion = ServerVersion.AutoDetect(connection.ConnectionString);
}
// There might occur different types of Exceptions while trying to AutoDetect() server version.
@@ -181,7 +185,7 @@ public static DbContextOptionsBuilder UseSingleStore(
var extension = (SingleStoreOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithServerVersion(serverVersion)
- .WithConnection(connection);
+ .WithDataSource(dataSource);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
ConfigureWarnings(optionsBuilder);
@@ -248,6 +252,21 @@ public static DbContextOptionsBuilder UseSingleStore(
=> (DbContextOptionsBuilder)UseSingleStore(
(DbContextOptionsBuilder)optionsBuilder, connection, mySqlOptionsAction);
+ ///
+ /// Configures the context to connect to a MySQL compatible database.
+ ///
+ /// The builder being used to configure the context.
+ /// A which will be used to get database connections.
+ /// An optional action to allow additional MySQL specific configuration.
+ /// The options builder so that further configuration can be chained.
+ public static DbContextOptionsBuilder UseSingleStore(
+ [NotNull] this DbContextOptionsBuilder optionsBuilder,
+ [NotNull] DbDataSource dataSource,
+ [CanBeNull] Action mySqlOptionsAction = null)
+ where TContext : DbContext
+ => (DbContextOptionsBuilder)UseSingleStore(
+ (DbContextOptionsBuilder)optionsBuilder, dataSource, mySqlOptionsAction);
+
private static SingleStoreOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.Options.FindExtension()
?? new SingleStoreOptionsExtension();
diff --git a/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs
index 4fbfef9d8..95ca81215 100644
--- a/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs
+++ b/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs
@@ -665,11 +665,11 @@ public static bool Like(
/// The pattern against which Full Text search is performed
/// true if there is a match.
/// Throws when query switched to client-evaluation.
- public static bool Match(
+ public static bool IsMatch(
[CanBeNull] this DbFunctions _,
[CanBeNull] string property,
[CanBeNull] string pattern)
- => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Match)));
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(IsMatch)));
///
///
@@ -685,7 +685,47 @@ public static bool Match(
/// The pattern against which Full Text search is performed
/// true if there is a match.
/// Throws when query switched to client-evaluation.
- public static bool Match(
+ public static bool IsMatch(
+ [CanBeNull] this DbFunctions _,
+ [NotNull] string[] properties,
+ [CanBeNull] string pattern)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(IsMatch)));
+
+ ///
+ ///
+ /// An implementation of the SQL MATCH operation for Full Text search.
+ ///
+ ///
+ /// The semantics of the comparison will depend on the database configuration.
+ /// In particular, it may be either case-sensitive or case-insensitive.
+ ///
+ ///
+ /// The DbFunctions instance.
+ /// The property of entity that is to be matched.
+ /// The pattern against which Full Text search is performed
+ /// The relevance value of the match.
+ /// Throws when query switched to client-evaluation.
+ public static double Match(
+ [CanBeNull] this DbFunctions _,
+ [CanBeNull] string property,
+ [CanBeNull] string pattern)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Match)));
+
+ ///
+ ///
+ /// An implementation of the SQL MATCH operation for Full Text search.
+ ///
+ ///
+ /// The semantics of the comparison will depend on the database configuration.
+ /// In particular, it may be either case-sensitive or case-insensitive.
+ ///
+ ///
+ /// The DbFunctions instance.
+ /// The propertys of entity that is to be matched.
+ /// The pattern against which Full Text search is performed
+ /// The relevance value of the match.
+ /// Throws when query switched to client-evaluation.
+ public static double Match(
[CanBeNull] this DbFunctions _,
[NotNull] string[] properties,
[CanBeNull] string pattern)
diff --git a/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs
index 2d075bad9..e50375d7e 100644
--- a/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs
+++ b/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs
@@ -88,7 +88,7 @@ internal static SingleStoreValueGenerationStrategy GetValueGenerationStrategy(
var annotation = property.FindAnnotation(SingleStoreAnnotationNames.ValueGenerationStrategy);
if (annotation?.Value is { } annotationValue
&& ObjectToEnumConverter.GetEnumValue(annotationValue) is { } enumValue
- && StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) == storeObject)
+ && StoreObjectIdentifier.Create(property.DeclaringType, storeObject.StoreObjectType) == storeObject)
{
return enumValue;
}
@@ -171,7 +171,7 @@ is StoreObjectIdentifier principal
private static SingleStoreValueGenerationStrategy GetDefaultValueGenerationStrategy(IReadOnlyProperty property)
{
- var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy();
+ var modelStrategy = property.DeclaringType.Model.GetValueGenerationStrategy();
if (modelStrategy == SingleStoreValueGenerationStrategy.IdentityColumn &&
IsCompatibleAutoIncrementColumn(property))
@@ -187,7 +187,7 @@ private static SingleStoreValueGenerationStrategy GetDefaultValueGenerationStrat
in StoreObjectIdentifier storeObject,
[CanBeNull] ITypeMappingSource typeMappingSource)
{
- var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy();
+ var modelStrategy = property.DeclaringType.Model.GetValueGenerationStrategy();
return modelStrategy == SingleStoreValueGenerationStrategy.IdentityColumn
&& IsCompatibleAutoIncrementColumn(property, storeObject, typeMappingSource)
@@ -323,7 +323,7 @@ public static void SetValueGenerationStrategy(
{
throw new ArgumentException(
SingleStoreStrings.IdentityBadType(
- property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName()));
+ property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName()));
}
if (value == SingleStoreValueGenerationStrategy.ComputedColumn
@@ -331,7 +331,7 @@ public static void SetValueGenerationStrategy(
{
throw new ArgumentException(
SingleStoreStrings.ComputedBadType(
- property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName()));
+ property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName()));
}
return value;
diff --git a/src/EFCore.SingleStore/Extensions/SingleStoreServiceCollectionExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStoreServiceCollectionExtensions.cs
index 4130f6f44..7163cfc3a 100644
--- a/src/EFCore.SingleStore/Extensions/SingleStoreServiceCollectionExtensions.cs
+++ b/src/EFCore.SingleStore/Extensions/SingleStoreServiceCollectionExtensions.cs
@@ -28,7 +28,6 @@
using EntityFrameworkCore.SingleStore.Migrations;
using EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal;
using EntityFrameworkCore.SingleStore.Query.Internal;
-using EntityFrameworkCore.SingleStore.Storage;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
@@ -111,7 +110,7 @@ public static IServiceCollection AddEntityFrameworkSingleStore([NotNull] this IS
.TryAdd()
.TryAdd()
.TryAdd()
- .TryAdd(p => p.GetService())
+ .TryAdd(p => p.GetRequiredService())
.TryAdd()
.TryAdd()
.TryAdd()
@@ -126,15 +125,22 @@ public static IServiceCollection AddEntityFrameworkSingleStore([NotNull] this IS
.TryAdd()
.TryAdd()
.TryAdd()
- .TryAdd(p => p.GetService())
+ .TryAdd(p => p.GetRequiredService())
//.TryAdd()
.TryAdd()
.TryAdd()
+
+ // TODO: Injecting this service will make our original JSON implementations work, but interferes with EF Core 8's new
+ // primitive collections support.
+ // We will need to limit the preprocessor logic to only the relevant cases.
+ .TryAdd()
+
.TryAdd()
.TryAdd()
.TryAddProviderSpecificServices(m => m
//.TryAddSingleton()
.TryAddSingleton()
+ .TryAddSingleton()
//.TryAddScoped()
.TryAddScoped()
.TryAddScoped());
diff --git a/src/EFCore.SingleStore/Infrastructure/Internal/ISingleStoreOptions.cs b/src/EFCore.SingleStore/Infrastructure/Internal/ISingleStoreOptions.cs
index d6630dcd7..1b082ad44 100644
--- a/src/EFCore.SingleStore/Infrastructure/Internal/ISingleStoreOptions.cs
+++ b/src/EFCore.SingleStore/Infrastructure/Internal/ISingleStoreOptions.cs
@@ -2,6 +2,7 @@
// Copyright (c) SingleStore Inc. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System.Data.Common;
using Microsoft.EntityFrameworkCore;
using EntityFrameworkCore.SingleStore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -11,6 +12,12 @@ namespace EntityFrameworkCore.SingleStore.Infrastructure.Internal
public interface ISingleStoreOptions : ISingletonOptions
{
SingleStoreConnectionSettings ConnectionSettings { get; }
+
+ ///
+ /// If null, there might still be a `DbDataSource` in the ApplicationServiceProvider.
+ /// >
+ DbDataSource DataSource { get; }
+
ServerVersion ServerVersion { get; }
CharSet DefaultCharSet { get; }
CharSet NationalCharSet { get; }
@@ -23,5 +30,6 @@ public interface ISingleStoreOptions : ISingletonOptions
SingleStoreJsonChangeTrackingOptions JsonChangeTrackingOptions { get; }
bool LimitKeyedOrIndexedStringColumnLength { get; }
bool StringComparisonTranslations { get; }
+ bool PrimitiveCollectionsSupport { get; }
}
}
diff --git a/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs b/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs
index 8945706b5..90827543a 100644
--- a/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs
+++ b/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs
@@ -8,8 +8,11 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
+using System.Data.Common;
using System.Globalization;
using Microsoft.EntityFrameworkCore;
+using SingleStoreConnector;
+using EntityFrameworkCore.SingleStore.Storage.Internal;
namespace EntityFrameworkCore.SingleStore.Infrastructure.Internal
{
@@ -30,6 +33,7 @@ public SingleStoreOptionsExtension()
public SingleStoreOptionsExtension([NotNull] SingleStoreOptionsExtension copyFrom)
: base(copyFrom)
{
+ DataSource = copyFrom.DataSource;
ServerVersion = copyFrom.ServerVersion;
NoBackslashEscapes = copyFrom.NoBackslashEscapes;
UpdateSqlModeOnOpen = copyFrom.UpdateSqlModeOnOpen;
@@ -54,6 +58,12 @@ public override DbContextOptionsExtensionInfo Info
protected override RelationalOptionsExtension Clone()
=> new SingleStoreOptionsExtension(this);
+ ///
+ /// The , or if a connection string or was used
+ /// instead of a .
+ ///
+ public virtual DbDataSource DataSource { get; private set; }
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -81,6 +91,36 @@ protected override RelationalOptionsExtension Clone()
public virtual bool IndexOptimizedBooleanColumns { get; private set; }
public virtual bool LimitKeyedOrIndexedStringColumnLength { get; private set; }
public virtual bool StringComparisonTranslations { get; private set; }
+ public virtual bool PrimitiveCollectionsSupport { get; private set; }
+
+ ///
+ /// Creates a new instance with all options the same as for this instance, but with the given option changed.
+ /// It is unusual to call this method directly. Instead use .
+ ///
+ /// The option to change.
+ /// A new instance with the option changed.
+ public virtual RelationalOptionsExtension WithDataSource(DbDataSource dataSource)
+ {
+ var clone = (SingleStoreOptionsExtension)Clone();
+ clone.DataSource = dataSource;
+ return clone;
+ }
+
+ ///
+ public override RelationalOptionsExtension WithConnectionString(string connectionString)
+ {
+ var clone = (SingleStoreOptionsExtension)base.WithConnectionString(connectionString);
+ clone.DataSource = null;
+ return clone;
+ }
+
+ ///
+ public override RelationalOptionsExtension WithConnection(DbConnection connection)
+ {
+ var clone = (SingleStoreOptionsExtension)base.WithConnection(connection);
+ clone.DataSource = null;
+ return clone;
+ }
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -169,6 +209,13 @@ public virtual SingleStoreOptionsExtension WithStringComparisonTranslations(bool
return clone;
}
+ public virtual SingleStoreOptionsExtension WithPrimitiveCollectionsSupport(bool enable)
+ {
+ var clone = (SingleStoreOptionsExtension)Clone();
+ clone.PrimitiveCollectionsSupport = enable;
+ return clone;
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -205,7 +252,7 @@ public override string LogFragment
if (Extension.ServerVersion != null)
{
- builder.Append("ServerVersion ")
+ builder.Append("ServerVersion=")
.Append(Extension.ServerVersion)
.Append(" ");
}
@@ -223,6 +270,7 @@ public override int GetServiceProviderHashCode()
{
var hashCode = new HashCode();
hashCode.Add(base.GetServiceProviderHashCode());
+ hashCode.Add(Extension.DataSource?.ConnectionString);
hashCode.Add(Extension.ServerVersion);
hashCode.Add(Extension.NoBackslashEscapes);
hashCode.Add(Extension.UpdateSqlModeOnOpen);
@@ -233,6 +281,7 @@ public override int GetServiceProviderHashCode()
hashCode.Add(Extension.IndexOptimizedBooleanColumns);
hashCode.Add(Extension.LimitKeyedOrIndexedStringColumnLength);
hashCode.Add(Extension.StringComparisonTranslations);
+ hashCode.Add(Extension.PrimitiveCollectionsSupport);
_serviceProviderHash = hashCode.ToHashCode();
}
@@ -243,6 +292,7 @@ public override int GetServiceProviderHashCode()
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo otherInfo &&
base.ShouldUseSameServiceProvider(other) &&
+ ReferenceEquals(Extension.DataSource, otherInfo.Extension.DataSource) &&
Equals(Extension.ServerVersion, otherInfo.Extension.ServerVersion) &&
Extension.NoBackslashEscapes == otherInfo.Extension.NoBackslashEscapes &&
Extension.UpdateSqlModeOnOpen == otherInfo.Extension.UpdateSqlModeOnOpen &&
@@ -252,7 +302,8 @@ public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo
Extension.SchemaNameTranslator == otherInfo.Extension.SchemaNameTranslator &&
Extension.IndexOptimizedBooleanColumns == otherInfo.Extension.IndexOptimizedBooleanColumns &&
Extension.LimitKeyedOrIndexedStringColumnLength == otherInfo.Extension.LimitKeyedOrIndexedStringColumnLength &&
- Extension.StringComparisonTranslations == otherInfo.Extension.StringComparisonTranslations;
+ Extension.StringComparisonTranslations == otherInfo.Extension.StringComparisonTranslations &&
+ Extension.PrimitiveCollectionsSupport == otherInfo.Extension.PrimitiveCollectionsSupport;
public override void PopulateDebugInfo(IDictionary debugInfo)
{
@@ -266,6 +317,7 @@ public override void PopulateDebugInfo(IDictionary debugInfo)
debugInfo["EntityFrameworkCore.SingleStore:" + nameof(SingleStoreDbContextOptionsBuilder.EnableIndexOptimizedBooleanColumns)] = HashCode.Combine(Extension.IndexOptimizedBooleanColumns).ToString(CultureInfo.InvariantCulture);
debugInfo["EntityFrameworkCore.SingleStore:" + nameof(SingleStoreDbContextOptionsBuilder.LimitKeyedOrIndexedStringColumnLength)] = HashCode.Combine(Extension.LimitKeyedOrIndexedStringColumnLength).ToString(CultureInfo.InvariantCulture);
debugInfo["EntityFrameworkCore.SingleStore:" + nameof(SingleStoreDbContextOptionsBuilder.EnableStringComparisonTranslations)] = HashCode.Combine(Extension.StringComparisonTranslations).ToString(CultureInfo.InvariantCulture);
+ debugInfo["EntityFrameworkCore.SingleStore:" + nameof(SingleStoreDbContextOptionsBuilder.EnablePrimitiveCollectionsSupport)] = HashCode.Combine(Extension.PrimitiveCollectionsSupport).ToString(CultureInfo.InvariantCulture);
}
}
}
diff --git a/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs b/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs
index 1b84c19ee..080c2eee3 100644
--- a/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs
+++ b/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs
@@ -90,5 +90,17 @@ public virtual bool PropertyOrVersion(string propertyNameOrServerVersion)
public virtual bool DescendingIndexes => false;
public virtual bool Returning => false;
public virtual bool CommonTableExpressions => false;
+ public virtual bool LimitWithinInAllAnySomeSubquery => false;
+ public virtual bool LimitWithNonConstantValue => false;
+ public virtual bool JsonTable => false;
+ public virtual bool JsonValue => false;
+ public virtual bool Values => false;
+ public virtual bool ValuesWithRows => false;
+ public virtual bool OffsetReferencesOuterQuery => false;
+
+ public virtual bool JsonTableImplementationStable => JsonTable;
+ public virtual bool JsonTableImplementationWithoutMySqlBugs => JsonTable;
+ public virtual bool JsonTableImplementationUsingParameterAsSourceWithoutEngineCrash => JsonTable;
+ public virtual bool JsonTableImplementationWithAggregate => JsonTable;
}
}
diff --git a/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs b/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs
index 20f9083fb..f6d0caa57 100644
--- a/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs
+++ b/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs
@@ -123,5 +123,13 @@ public virtual SingleStoreDbContextOptionsBuilder LimitKeyedOrIndexedStringColum
///
public virtual SingleStoreDbContextOptionsBuilder EnableStringComparisonTranslations(bool enable = true)
=> WithOption(e => e.WithStringComparisonTranslations(enable));
+
+ ///
+ /// Configures the context to translate using primitive collections. At the time of the Pomelo 8.0.0 release, MySQL Server can
+ /// crash when using primitive collections with JSON and MariaDB support is incomplete. Support and translations in regards to
+ /// this option can change at any time in the future. This optin is disabled by default. Enabled at your own risk.
+ ///
+ public virtual SingleStoreDbContextOptionsBuilder EnablePrimitiveCollectionsSupport(bool enable = true)
+ => WithOption(e => e.WithPrimitiveCollectionsSupport(enable));
}
}
diff --git a/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs b/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs
index c390804f6..62d4fc01c 100644
--- a/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs
+++ b/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs
@@ -16,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore
public class SingleStoreServerVersion : ServerVersion
{
public static readonly string SingleStoreTypeIdentifier = nameof(ServerType.SingleStore).ToLowerInvariant();
- public static readonly ServerVersion LatestSupportedServerVersion = new SingleStoreServerVersion(new Version(8, 7, 0));
+ public static readonly ServerVersion LatestSupportedServerVersion = new SingleStoreServerVersion(new Version(8, 9, 0));
public override ServerVersionSupport Supports { get; }
@@ -90,6 +90,17 @@ internal SingleStoreServerVersionSupport([NotNull] ServerVersion serverVersion)
public override bool ConnectionAttributes => ServerVersion.Version >= new Version(8, 1, 0);
public override bool DescendingIndexes => false;
public override bool CommonTableExpressions => false;
+ public override bool LimitWithinInAllAnySomeSubquery => false;
+ public override bool LimitWithNonConstantValue => false;
+ public override bool JsonTable => false;
+ public override bool JsonValue => false;
+ public override bool Values => false;
+ public override bool ValuesWithRows => false;
+ public override bool OffsetReferencesOuterQuery => false;
+
+ public override bool JsonTableImplementationStable => false;
+ public override bool JsonTableImplementationWithoutMySqlBugs => false; // Other non-fatal bugs regarding JSON_TABLE.
+ public override bool JsonTableImplementationUsingParameterAsSourceWithoutEngineCrash => false; // MySQL non-deterministically crashes when using a parameter with JSON as the source of a JSON_TABLE call.
}
}
}
diff --git a/src/EFCore.SingleStore/Internal/SingleStoreLoggerExtensions.cs b/src/EFCore.SingleStore/Internal/SingleStoreLoggerExtensions.cs
index 9b39a77a6..f7188b72c 100644
--- a/src/EFCore.SingleStore/Internal/SingleStoreLoggerExtensions.cs
+++ b/src/EFCore.SingleStore/Internal/SingleStoreLoggerExtensions.cs
@@ -24,7 +24,7 @@ private static string DecimalTypeKeyWarning(EventDefinitionBase definition, Even
var p = (PropertyEventData)payload;
return d.GenerateMessage(
p.Property.Name,
- p.Property.DeclaringEntityType.DisplayName());
+ p.Property.DeclaringType.DisplayName());
}
///
@@ -41,7 +41,7 @@ public static void DecimalTypeDefaultWarning(
if (diagnostics.ShouldLog(definition))
{
- definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName());
+ definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName());
}
if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
@@ -61,7 +61,7 @@ private static string DecimalTypeDefaultWarning(EventDefinitionBase definition,
var p = (PropertyEventData)payload;
return d.GenerateMessage(
p.Property.Name,
- p.Property.DeclaringEntityType.DisplayName());
+ p.Property.DeclaringType.DisplayName());
}
///
@@ -78,7 +78,7 @@ public static void ByteIdentityColumnWarning(
if (diagnostics.ShouldLog(definition))
{
- definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName());
+ definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName());
}
if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
@@ -98,7 +98,7 @@ private static string ByteIdentityColumnWarning(EventDefinitionBase definition,
var p = (PropertyEventData)payload;
return d.GenerateMessage(
p.Property.Name,
- p.Property.DeclaringEntityType.DisplayName());
+ p.Property.DeclaringType.DisplayName());
}
///
diff --git a/src/EFCore.SingleStore/Internal/SingleStoreModelValidator.cs b/src/EFCore.SingleStore/Internal/SingleStoreModelValidator.cs
index 234dbf46b..cd5f8b75e 100644
--- a/src/EFCore.SingleStore/Internal/SingleStoreModelValidator.cs
+++ b/src/EFCore.SingleStore/Internal/SingleStoreModelValidator.cs
@@ -42,6 +42,20 @@ public SingleStoreModelValidator(
{
}
+ ///
+ protected override void ValidateJsonEntities(
+ IModel model,
+ IDiagnosticsLogger logger)
+ {
+ foreach (var entityType in model.GetEntityTypes())
+ {
+ if (entityType.IsMappedToJson())
+ {
+ throw new InvalidOperationException(SingleStoreStrings.Ef7CoreJsonMappingNotSupported);
+ }
+ }
+ }
+
///
protected override void ValidateStoredProcedures(
IModel model,
diff --git a/src/EFCore.SingleStore/Internal/SingleStoreOptions.cs b/src/EFCore.SingleStore/Internal/SingleStoreOptions.cs
index a41240725..c80ba4019 100644
--- a/src/EFCore.SingleStore/Internal/SingleStoreOptions.cs
+++ b/src/EFCore.SingleStore/Internal/SingleStoreOptions.cs
@@ -3,6 +3,7 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Data.Common;
using System.Linq;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Storage.Internal;
@@ -10,6 +11,7 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using EntityFrameworkCore.SingleStore.Infrastructure;
using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.Extensions.DependencyInjection;
using SingleStoreConnector;
namespace EntityFrameworkCore.SingleStore.Internal
@@ -21,6 +23,7 @@ public class SingleStoreOptions : ISingleStoreOptions
public SingleStoreOptions()
{
ConnectionSettings = new SingleStoreConnectionSettings();
+ DataSource = null;
ServerVersion = null;
// We explicitly use `utf8` in all instances, where charset based calculations need to be done, but accessing annotations
@@ -44,6 +47,7 @@ public SingleStoreOptions()
LimitKeyedOrIndexedStringColumnLength = true;
StringComparisonTranslations = false;
+ PrimitiveCollectionsSupport = false;
}
public virtual void Initialize(IDbContextOptions options)
@@ -52,6 +56,7 @@ public virtual void Initialize(IDbContextOptions options)
var mySqlJsonOptions = (SingleStoreJsonOptionsExtension)options.Extensions.LastOrDefault(e => e is SingleStoreJsonOptionsExtension);
ConnectionSettings = GetConnectionSettings(mySqlOptions);
+ DataSource = mySqlOptions.DataSource;
ServerVersion = mySqlOptions.ServerVersion ?? throw new InvalidOperationException($"The {nameof(ServerVersion)} has not been set.");
NoBackslashEscapes = mySqlOptions.NoBackslashEscapes;
ReplaceLineBreaksWithCharFunction = mySqlOptions.ReplaceLineBreaksWithCharFunction;
@@ -63,6 +68,7 @@ public virtual void Initialize(IDbContextOptions options)
JsonChangeTrackingOptions = mySqlJsonOptions?.JsonChangeTrackingOptions ?? default;
LimitKeyedOrIndexedStringColumnLength = mySqlOptions.LimitKeyedOrIndexedStringColumnLength;
StringComparisonTranslations = mySqlOptions.StringComparisonTranslations;
+ PrimitiveCollectionsSupport = mySqlOptions.PrimitiveCollectionsSupport;
}
public virtual void Validate(IDbContextOptions options)
@@ -71,6 +77,24 @@ public virtual void Validate(IDbContextOptions options)
var mySqlJsonOptions = (SingleStoreJsonOptionsExtension)options.Extensions.LastOrDefault(e => e is SingleStoreJsonOptionsExtension);
var connectionSettings = GetConnectionSettings(mySqlOptions);
+ //
+ // CHECK: To we have to ensure that the ApplicationServiceProvider itself is not replaced, because we rely on it in our
+ // DbDataSource check, or is that not possible?
+ //
+
+ // Even though we only save a DbDataSource that has been explicitly set using the SingleStoreOptionsExtensions here in SingleStoreOptions,
+ // we will later also fall back to a DbDataSource that has been added as a service to the ApplicationServiceProvider, if no
+ // DbDataSource has been explicitly set here. We call that DbDataSource the "effective" DbDataSource and handle it in the same
+ // way we would handle a singleton option.
+ var effectiveDataSource = mySqlOptions.DataSource ??
+ options.FindExtension()?.ApplicationServiceProvider?.GetService();
+ if (effectiveDataSource is not null &&
+ !ReferenceEquals(DataSource, mySqlOptions.DataSource))
+ {
+ throw new InvalidOperationException(
+ SingleStoreStrings.TwoDataSourcesInSameServiceProvider(nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
+ }
+
if (!Equals(ServerVersion, mySqlOptions.ServerVersion))
{
throw new InvalidOperationException(
@@ -162,6 +186,14 @@ public virtual void Validate(IDbContextOptions options)
nameof(SingleStoreDbContextOptionsBuilder.EnableStringComparisonTranslations),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}
+
+ if (!Equals(PrimitiveCollectionsSupport, mySqlOptions.PrimitiveCollectionsSupport))
+ {
+ throw new InvalidOperationException(
+ CoreStrings.SingletonOptionChanged(
+ nameof(SingleStoreDbContextOptionsBuilder.EnablePrimitiveCollectionsSupport),
+ nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
+ }
}
protected virtual SingleStoreDefaultDataTypeMappings ApplyDefaultDataTypeMappings(SingleStoreDefaultDataTypeMappings defaultDataTypeMappings, SingleStoreConnectionSettings connectionSettings)
@@ -221,6 +253,7 @@ private static SingleStoreConnectionSettings GetConnectionSettings(SingleStoreOp
protected virtual bool Equals(SingleStoreOptions other)
{
return Equals(ConnectionSettings, other.ConnectionSettings) &&
+ ReferenceEquals(DataSource, other.DataSource) &&
Equals(ServerVersion, other.ServerVersion) &&
Equals(DefaultCharSet, other.DefaultCharSet) &&
Equals(NationalCharSet, other.NationalCharSet) &&
@@ -232,7 +265,8 @@ protected virtual bool Equals(SingleStoreOptions other)
IndexOptimizedBooleanColumns == other.IndexOptimizedBooleanColumns &&
JsonChangeTrackingOptions == other.JsonChangeTrackingOptions &&
LimitKeyedOrIndexedStringColumnLength == other.LimitKeyedOrIndexedStringColumnLength &&
- StringComparisonTranslations == other.StringComparisonTranslations;
+ StringComparisonTranslations == other.StringComparisonTranslations &&
+ PrimitiveCollectionsSupport == other.PrimitiveCollectionsSupport;
}
public override bool Equals(object obj)
@@ -260,6 +294,7 @@ public override int GetHashCode()
var hashCode = new HashCode();
hashCode.Add(ConnectionSettings);
+ hashCode.Add(DataSource?.ConnectionString);
hashCode.Add(ServerVersion);
hashCode.Add(DefaultCharSet);
hashCode.Add(NationalCharSet);
@@ -272,11 +307,18 @@ public override int GetHashCode()
hashCode.Add(JsonChangeTrackingOptions);
hashCode.Add(LimitKeyedOrIndexedStringColumnLength);
hashCode.Add(StringComparisonTranslations);
+ hashCode.Add(PrimitiveCollectionsSupport);
return hashCode.ToHashCode();
}
public virtual SingleStoreConnectionSettings ConnectionSettings { get; private set; }
+
+ ///
+ /// If null, there might still be a `DbDataSource` in the ApplicationServiceProvider.
+ ///
+ public virtual DbDataSource DataSource { get; private set; }
+
public virtual ServerVersion ServerVersion { get; private set; }
public virtual CharSet DefaultCharSet { get; private set; }
public virtual CharSet NationalCharSet { get; }
@@ -289,5 +331,6 @@ public override int GetHashCode()
public virtual SingleStoreJsonChangeTrackingOptions JsonChangeTrackingOptions { get; private set; }
public virtual bool LimitKeyedOrIndexedStringColumnLength { get; private set; }
public virtual bool StringComparisonTranslations { get; private set; }
+ public virtual bool PrimitiveCollectionsSupport { get; private set; }
}
}
diff --git a/src/EFCore.SingleStore/Metadata/Builders/SingleStoreEntityTypeBuilder.cs b/src/EFCore.SingleStore/Metadata/Builders/SingleStoreEntityTypeBuilder.cs
deleted file mode 100644
index 1088ad35f..000000000
--- a/src/EFCore.SingleStore/Metadata/Builders/SingleStoreEntityTypeBuilder.cs
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright (c) Pomelo Foundation. All rights reserved.
-// Copyright (c) SingleStore Inc. All rights reserved.
-// Licensed under the MIT. See LICENSE in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq.Expressions;
-using System.Reflection;
-using Microsoft.EntityFrameworkCore.ChangeTracking;
-using Microsoft.EntityFrameworkCore.Diagnostics;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-using Microsoft.EntityFrameworkCore.Utilities;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-
-namespace Pomelo.EntityFrameworkCore.SingleStore.Metadata.Builders
-{
- public class SingleStoreEntityTypeBuilder : EntityTypeBuilder
- {
- public SingleStoreEntityTypeBuilder(IMutableEntityType entityType) : base(entityType)
- {
-
- }
-
- private InternalEntityTypeBuilder Builder { [DebuggerStepThrough] get; }
-
- ///
- ///
- /// Configures a relationship where the target entity is owned by (or part of) this entity.
- ///
- ///
- /// The target entity type for each ownership relationship is treated as a different entity type
- /// even if the navigation is of the same type. Configuration of the target entity type
- /// isn't applied to the target entity type of other ownership relationships.
- ///
- ///
- /// Most operations on an owned entity require accessing it through the owner entity using the corresponding navigation.
- ///
- ///
- /// After calling this method, you should chain a call to
- /// to fully configure the relationship.
- ///
- ///
- /// The name of the entity type that this relationship targets.
- ///
- /// The name of the reference navigation property on this entity type that represents the relationship.
- ///
- /// An object that can be used to configure the owned type and the relationship.
- public override OwnedNavigationBuilder OwnsOne(
- string ownedTypeName,
- string navigationName)
- => OwnsOneBuilder(
- new TypeIdentity(Check.NotEmpty(ownedTypeName, nameof(ownedTypeName))),
- Check.NotEmpty(navigationName, nameof(navigationName)));
-
- ///
- ///
- /// Configures a relationship where the target entity is owned by (or part of) this entity.
- ///
- ///
- /// The target entity type for each ownership relationship is treated as a different entity type
- /// even if the navigation is of the same type. Configuration of the target entity type
- /// isn't applied to the target entity type of other ownership relationships.
- ///
- ///
- /// Most operations on an owned entity require accessing it through the owner entity using the corresponding navigation.
- ///
- ///
- /// After calling this method, you should chain a call to
- /// to fully configure the relationship.
- ///
- ///
- /// The name of the entity type that this relationship targets.
- /// The CLR type of the entity type that this relationship targets.
- ///
- /// The name of the reference navigation property on this entity type that represents the relationship.
- ///
- /// An object that can be used to configure the owned type and the relationship.
- public override OwnedNavigationBuilder OwnsOne(
- string ownedTypeName,
- Type ownedType,
- string navigationName)
- => OwnsOneBuilder(
- new TypeIdentity(Check.NotEmpty(ownedTypeName, nameof(ownedTypeName)), ownedType),
- Check.NotEmpty(navigationName, nameof(navigationName)));
-
- ///
- ///
- /// Configures a relationship where the target entity is owned by (or part of) this entity.
- ///
- ///
- /// The target entity type for each ownership relationship is treated as a different entity type
- /// even if the navigation is of the same type. Configuration of the target entity type
- /// isn't applied to the target entity type of other ownership relationships.
- ///
- ///
- /// Most operations on an owned entity require accessing it through the owner entity using the corresponding navigation.
- ///
- ///
- /// After calling this method, you should chain a call to
- /// to fully configure the relationship.
- ///
- ///
- /// The entity type that this relationship targets.
- ///
- /// The name of the reference navigation property on this entity type that represents the relationship.
- ///
- /// An object that can be used to configure the owned type and the relationship.
- public override OwnedNavigationBuilder OwnsOne(
- Type ownedType,
- string navigationName)
- => OwnsOneBuilder(
- new TypeIdentity(Check.NotNull(ownedType, nameof(ownedType)), (Model)Metadata.Model),
- Check.NotEmpty(navigationName, nameof(navigationName)));
-
- ///
- ///
- /// Configures a relationship where the target entity is owned by (or part of) this entity.
- ///
- ///
- /// The target entity type for each ownership relationship is treated as a different entity type
- /// even if the navigation is of the same type. Configuration of the target entity type
- /// isn't applied to the target entity type of other ownership relationships.
- ///
- ///
- /// Most operations on an owned entity require accessing it through the owner entity using the corresponding navigation.
- ///
- ///
- /// After calling this method, you should chain a call to
- /// to fully configure the relationship.
- ///
- ///
- /// The name of the entity type that this relationship targets.
- ///
- /// The name of the reference navigation property on this entity type that represents the relationship.
- ///
- /// An action that performs configuration of the owned type and the relationship.
- /// An object that can be used to configure the entity type.
- public override EntityTypeBuilder OwnsOne(
- string ownedTypeName,
- string navigationName,
- Action buildAction)
- {
- Check.NotEmpty(ownedTypeName, nameof(ownedTypeName));
- Check.NotEmpty(navigationName, nameof(navigationName));
- Check.NotNull(buildAction, nameof(buildAction));
-
- buildAction(OwnsOneBuilder(new TypeIdentity(ownedTypeName), navigationName));
- return this;
- }
-
- ///
- ///
- /// Configures a relationship where the target entity is owned by (or part of) this entity.
- ///
- ///
- /// The target entity type for each ownership relationship is treated as a different entity type
- /// even if the navigation is of the same type. Configuration of the target entity type
- /// isn't applied to the target entity type of other ownership relationships.
- ///
- ///
- /// Most operations on an owned entity require accessing it through the owner entity using the corresponding navigation.
- ///
- ///
- /// After calling this method, you should chain a call to
- /// to fully configure the relationship.
- ///
- ///
- /// The name of the entity type that this relationship targets.
- /// The CLR type of the entity type that this relationship targets.
- ///
- /// The name of the reference navigation property on this entity type that represents the relationship.
- ///
- /// An action that performs configuration of the owned type and the relationship.
- /// An object that can be used to configure the entity type.
- public override EntityTypeBuilder OwnsOne(
- string ownedTypeName,
- Type ownedType,
- string navigationName,
- Action buildAction)
- {
- Check.NotEmpty(ownedTypeName, nameof(ownedTypeName));
- Check.NotNull(ownedType, nameof(ownedType));
- Check.NotEmpty(navigationName, nameof(navigationName));
- Check.NotNull(buildAction, nameof(buildAction));
-
- buildAction(OwnsOneBuilder(new TypeIdentity(ownedTypeName, ownedType), navigationName));
- return this;
- }
-
- ///
- ///
- /// Configures a relationship where the target entity is owned by (or part of) this entity.
- ///
- ///
- /// The target entity type for each ownership relationship is treated as a different entity type
- /// even if the navigation is of the same type. Configuration of the target entity type
- /// isn't applied to the target entity type of other ownership relationships.
- ///
- ///
- /// Most operations on an owned entity require accessing it through the owner entity using the corresponding navigation.
- ///
- ///
- /// After calling this method, you should chain a call to
- /// to fully configure the relationship.
- ///
- ///
- /// The entity type that this relationship targets.
- ///
- /// The name of the reference navigation property on this entity type that represents the relationship.
- ///
- /// An action that performs configuration of the owned type and the relationship.
- /// An object that can be used to configure the entity type.
- public override EntityTypeBuilder OwnsOne(
- Type ownedType,
- string navigationName,
- Action buildAction)
- {
- Check.NotNull(ownedType, nameof(ownedType));
- Check.NotEmpty(navigationName, nameof(navigationName));
- Check.NotNull(buildAction, nameof(buildAction));
-
- buildAction(OwnsOneBuilder(new TypeIdentity(ownedType, (Model)Metadata.Model), navigationName));
- return this;
- }
-
- private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string navigationName)
- {
- IMutableForeignKey foreignKey;
- using (var batch = Builder.Metadata.Model.DelayConventions())
- {
- var navigationMember = new MemberIdentity(navigationName);
- var relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit)!;
- relationship.IsUnique(false, ConfigurationSource.Explicit);
- foreignKey = (IMutableForeignKey)batch.Run(relationship.Metadata)!;
- }
-
- return new OwnedNavigationBuilder(foreignKey);
- }
- }
-}
diff --git a/src/EFCore.SingleStore/Metadata/Conventions/TableCharSetAttributeConvention.cs b/src/EFCore.SingleStore/Metadata/Conventions/TableCharSetAttributeConvention.cs
index 57108b418..75f94c7ba 100644
--- a/src/EFCore.SingleStore/Metadata/Conventions/TableCharSetAttributeConvention.cs
+++ b/src/EFCore.SingleStore/Metadata/Conventions/TableCharSetAttributeConvention.cs
@@ -12,7 +12,7 @@ namespace EntityFrameworkCore.SingleStore.Metadata.Conventions
///
/// A convention that configures the character set for an entity based on the applied .
///
- public class TableCharSetAttributeConvention : EntityTypeAttributeConventionBase
+ public class TableCharSetAttributeConvention : TypeAttributeConventionBase
{
///
/// Creates a new instance of .
diff --git a/src/EFCore.SingleStore/Metadata/Conventions/TableCollationAttributeConvention.cs b/src/EFCore.SingleStore/Metadata/Conventions/TableCollationAttributeConvention.cs
index 3e72eba54..3cc96efbd 100644
--- a/src/EFCore.SingleStore/Metadata/Conventions/TableCollationAttributeConvention.cs
+++ b/src/EFCore.SingleStore/Metadata/Conventions/TableCollationAttributeConvention.cs
@@ -12,7 +12,7 @@ namespace EntityFrameworkCore.SingleStore.Metadata.Conventions
///
/// A convention that configures the collation for an entity based on the applied .
///
- public class TableCollationAttributeConvention : EntityTypeAttributeConventionBase
+ public class TableCollationAttributeConvention : TypeAttributeConventionBase
{
///
/// Creates a new instance of .
diff --git a/src/EFCore.SingleStore/Metadata/Internal/ObjectToEnumConverter.cs b/src/EFCore.SingleStore/Metadata/Internal/ObjectToEnumConverter.cs
index b295cede3..fc126a3a3 100644
--- a/src/EFCore.SingleStore/Metadata/Internal/ObjectToEnumConverter.cs
+++ b/src/EFCore.SingleStore/Metadata/Internal/ObjectToEnumConverter.cs
@@ -13,7 +13,7 @@ public static class ObjectToEnumConverter
/// be setup without provider specific dependencies.
///
///
- /// See https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/1205 for further information.
+ /// See https://github.com/PomeloFoundation/EntityFrameworkCore.MySql/issues/1205 for further information.
///
public static T? GetEnumValue(object value)
where T : struct
diff --git a/src/EFCore.SingleStore/Metadata/Internal/SingleStoreAnnotationProvider.cs b/src/EFCore.SingleStore/Metadata/Internal/SingleStoreAnnotationProvider.cs
index 018d58e61..a61faea2d 100644
--- a/src/EFCore.SingleStore/Metadata/Internal/SingleStoreAnnotationProvider.cs
+++ b/src/EFCore.SingleStore/Metadata/Internal/SingleStoreAnnotationProvider.cs
@@ -61,7 +61,7 @@ public override IEnumerable For(ITable table, bool designTime)
}
// Model validation ensures that these facets are the same on all mapped entity types
- var entityType = table.EntityTypeMappings.First().EntityType;
+ var entityType = (IEntityType)table.EntityTypeMappings.First().TypeBase;
// Use an explicitly defined character set, if set.
// Otherwise, explicitly use the model/database character set, if delegation is enabled.
@@ -167,7 +167,7 @@ public override IEnumerable For(IColumn column, bool designTime)
if (column.PropertyMappings.Where(
m => (m.TableMapping.IsSharedTablePrincipal ?? true) &&
- m.TableMapping.EntityType == m.Property.DeclaringEntityType)
+ m.TableMapping.TypeBase == m.Property.DeclaringType)
.Select(m => m.Property)
.FirstOrDefault(p => p.GetValueGenerationStrategy(table) == SingleStoreValueGenerationStrategy.IdentityColumn) is IProperty identityProperty)
{
@@ -409,7 +409,8 @@ protected virtual string GetActualPropertyCharSet(IProperty[] properties, Delega
properties.Select(
p => p.FindTypeMapping() is SingleStoreStringTypeMapping {IsNationalChar: false}
// An explicitly defined collation on the current property level takes precedence over an inherited charset.
- ? GetActualEntityTypeCharSet(p.DeclaringEntityType, currentLevel) is string charSet &&
+ ? p.DeclaringType is IEntityType entityType &&
+ GetActualEntityTypeCharSet(entityType, currentLevel) is string charSet &&
(p.GetCollation() is not string collation ||
collation.StartsWith(charSet, StringComparison.OrdinalIgnoreCase))
? charSet
@@ -436,10 +437,11 @@ protected virtual string GetActualPropertyCollation(IProperty[] properties, Dele
? properties.Select(p => p.GetSingleStoreLegacyCollation()).FirstOrDefault(c => c is not null) ??
properties.Select(
// An explicitly defined charset on the current property level takes precedence over an inherited collation.
- p => (p.FindTypeMapping() is SingleStoreStringTypeMapping {IsNationalChar: false}
- ? GetActualEntityTypeCollation(p.DeclaringEntityType, currentLevel)
+ p => (p.FindTypeMapping() is SingleStoreStringTypeMapping {IsNationalChar: false} &&
+ p.DeclaringType is IEntityType entityType
+ ? GetActualEntityTypeCollation(entityType, currentLevel)
: p.FindTypeMapping() is SingleStoreGuidTypeMapping {IsCharBasedStoreType: true}
- ? p.DeclaringEntityType.Model.GetActualGuidCollation(_options.DefaultGuidCollation)
+ ? p.DeclaringType.Model.GetActualGuidCollation(_options.DefaultGuidCollation)
: null) is string collation &&
(p.GetCharSet() is not string charSet ||
collation.StartsWith(charSet, StringComparison.OrdinalIgnoreCase))
diff --git a/src/EFCore.SingleStore/Migrations/SingleStoreMigrationsSqlGenerator.cs b/src/EFCore.SingleStore/Migrations/SingleStoreMigrationsSqlGenerator.cs
index 03087f1b3..456c035f3 100644
--- a/src/EFCore.SingleStore/Migrations/SingleStoreMigrationsSqlGenerator.cs
+++ b/src/EFCore.SingleStore/Migrations/SingleStoreMigrationsSqlGenerator.cs
@@ -123,7 +123,7 @@ private void UpdateMigrationOperations(IReadOnlyList operati
}
catch (InvalidOperationException)
{
- throw new InvalidOperationException("Feature 'more than one FULLTEXT KEY' is not supported by SingleStore.");
+ throw new InvalidOperationException("Feature 'more than one FULLTEXT KEY' is not supported by SingleStore Distributed.");
}
}
}
@@ -434,16 +434,26 @@ protected override void Generate(
{
Check.NotNull(operation, nameof(operation));
Check.NotNull(builder, nameof(builder));
+
if (!_options.ServerVersion.Supports.Sequences)
{
throw new InvalidOperationException(
$"Cannot restart sequence '{operation.Name}' because sequences are not supported in server version {_options.ServerVersion}.");
}
+
builder
.Append("ALTER SEQUENCE ")
- .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema))
- .Append(" RESTART WITH ")
- .Append(IntegerConstant(operation.StartValue))
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema));
+
+ if (operation.StartValue.HasValue)
+ {
+ builder
+ .Append(" START WITH ")
+ .Append(IntegerConstant(operation.StartValue));
+ }
+
+ builder
+ .Append(" RESTART")
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
EndStatement(builder);
@@ -1062,7 +1072,7 @@ protected override void DefaultValue(
if (typeMapping is IDefaultValueCompatibilityAware defaultValueCompatibilityAware)
{
- typeMapping = defaultValueCompatibilityAware.Clone(true);
+ typeMapping = defaultValueCompatibilityAware.Clone(isDefaultValueCompatible: true);
}
var sqlLiteralDefaultValue = typeMapping.GenerateSqlLiteral(defaultValue);
@@ -1184,7 +1194,7 @@ protected override void Generate(
MigrationCommandListBuilder builder,
bool terminate = true)
{
- // Feature 'FOREIGN KEY' is not supported by SingleStore.
+ // Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.
}
protected override void ForeignKeyConstraint(
@@ -1192,7 +1202,7 @@ protected override void ForeignKeyConstraint(
IModel model,
MigrationCommandListBuilder builder)
{
- // Feature 'FOREIGN KEY' is not supported by SingleStore.
+ // Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.
}
protected override void PrimaryKeyConstraint(
@@ -1395,7 +1405,7 @@ private string ColumnListWithIndexPrefixLengthAndSortOrder(MigrationOperation op
protected virtual string ColumnList([NotNull] string[] columns, Func columnPostfix)
=> string.Join(", ", columns.Select((c, i) => Dependencies.SqlGenerationHelper.DelimitIdentifier(c) + columnPostfix?.Invoke(c, i)));
- private string IntegerConstant(long value)
+ private string IntegerConstant(long? value)
=> string.Format(CultureInfo.InvariantCulture, "{0}", value);
private static string Truncate(string source, int maxLength)
diff --git a/src/EFCore.SingleStore/Properties/SingleStoreStrings.Designer.cs b/src/EFCore.SingleStore/Properties/SingleStoreStrings.Designer.cs
index 25a08431b..bcd79474c 100644
--- a/src/EFCore.SingleStore/Properties/SingleStoreStrings.Designer.cs
+++ b/src/EFCore.SingleStore/Properties/SingleStoreStrings.Designer.cs
@@ -21,6 +21,14 @@ public static class SingleStoreStrings
private static readonly ResourceManager _resourceManager
= new ResourceManager("EntityFrameworkCore.SingleStore.Properties.SingleStoreStrings", typeof(SingleStoreStrings).GetTypeInfo().Assembly);
+ ///
+ /// Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'.
+ ///
+ public static string TwoDataSourcesInSameServiceProvider(object useInternalServiceProvider)
+ => string.Format(
+ GetString("TwoDataSourcesInSameServiceProvider", nameof(useInternalServiceProvider)),
+ useInternalServiceProvider);
+
///
/// Identity value generation cannot be used for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Identity value generation can only be used with integer, DateTime, and DateTimeOffset properties.
///
@@ -177,6 +185,12 @@ public static string StoredProcedureOutputParametersNotSupported(object entityTy
GetString("StoredProcedureOutputParametersNotSupported", nameof(entityType), nameof(sproc)),
entityType, sproc);
+ ///
+ /// The EF Core 7.0 JSON support isn't currently implemented. Instead, there is support for a more extensive implementation. See https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql for more information on how to map JSON.
+ ///
+ public static string Ef7CoreJsonMappingNotSupported
+ => GetString("Ef7CoreJsonMappingNotSupported");
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/EFCore.SingleStore/Properties/SingleStoreStrings.resx b/src/EFCore.SingleStore/Properties/SingleStoreStrings.resx
index a5afc4c28..211dd0656 100644
--- a/src/EFCore.SingleStore/Properties/SingleStoreStrings.resx
+++ b/src/EFCore.SingleStore/Properties/SingleStoreStrings.resx
@@ -117,6 +117,9 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'.
+
Identity value generation cannot be used for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Identity value generation can only be used with integer, DateTime, and DateTimeOffset properties.
@@ -243,4 +246,7 @@
The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with an output parameter. SingleStore stored procedures do not support output parameters; use result columns or return values instead.
+
+ The EF Core 7.0 JSON support isn't currently implemented. Instead, there is support for a more extensive implementation. See https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql for more information on how to map JSON.
+
diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs
index ada6e0bbb..de4ea223c 100644
--- a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs
@@ -72,6 +72,16 @@ private static readonly MethodInfo[] _likeMethodInfos
&& method.GetParameters().Length is >= 3 and <= 4)
.SelectMany(method => _supportedLikeTypes.Select(type => method.MakeGenericMethod(type))).ToArray();
+ private static readonly MethodInfo _isMatchMethodInfo
+ = typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod(
+ nameof(SingleStoreDbFunctionsExtensions.IsMatch),
+ new[] {typeof(DbFunctions), typeof(string), typeof(string)});
+
+ private static readonly MethodInfo _isMatchWithMultiplePropertiesMethodInfo
+ = typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod(
+ nameof(SingleStoreDbFunctionsExtensions.IsMatch),
+ new[] {typeof(DbFunctions), typeof(string[]), typeof(string)});
+
private static readonly MethodInfo _matchMethodInfo
= typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod(
nameof(SingleStoreDbFunctionsExtensions.Match),
@@ -159,6 +169,14 @@ public virtual SqlExpression Translate(
excapeChar);
}
+ if (Equals(method, _isMatchMethodInfo) ||
+ Equals(method, _isMatchWithMultiplePropertiesMethodInfo))
+ {
+ return _sqlExpressionFactory.GreaterThan(
+ _sqlExpressionFactory.MakeMatch(arguments[1], arguments[2]),
+ _sqlExpressionFactory.Constant(0));
+ }
+
if (Equals(method, _matchMethodInfo) ||
Equals(method, _matchWithMultiplePropertiesMethodInfo))
{
diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonPocoTranslator.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonPocoTranslator.cs
index dc2415d39..c443642c0 100644
--- a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonPocoTranslator.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonPocoTranslator.cs
@@ -28,7 +28,7 @@ public SingleStoreJsonPocoTranslator(
{
_typeMappingSource = typeMappingSource;
_sqlExpressionFactory = sqlExpressionFactory;
- _unquotedStringTypeMapping = ((SingleStoreStringTypeMapping)_typeMappingSource.FindMapping(typeof(string))).Clone(true);
+ _unquotedStringTypeMapping = ((SingleStoreStringTypeMapping)_typeMappingSource.FindMapping(typeof(string))).Clone(unquoted: true);
_intTypeMapping = _typeMappingSource.FindMapping(typeof(int));
}
diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonTableExpression.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonTableExpression.cs
new file mode 100644
index 000000000..2a143a00e
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreJsonTableExpression.cs
@@ -0,0 +1,269 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal;
+
+///
+/// An expression that represents a MySQL JSON_TABLE() function call in a SQL tree.
+///
+public class SingleStoreJsonTableExpression : TableValuedFunctionExpression, IClonableTableExpressionBase
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual SqlExpression JsonExpression
+ => Arguments[0];
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual IReadOnlyList Path { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual IReadOnlyList ColumnInfos { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+
+ public SingleStoreJsonTableExpression(
+ string alias,
+ SqlExpression jsonExpression,
+ IReadOnlyList path = null,
+ IReadOnlyList columnInfos = null)
+ : base(alias, "JSON_TABLE", schema: null, builtIn: true, new[] { jsonExpression })
+ {
+ Path = path;
+ ColumnInfos = columnInfos;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
+ var visitedJsonExpression = (SqlExpression)visitor.Visit(JsonExpression);
+
+ PathSegment[] visitedPath = null;
+
+ if (Path is not null)
+ {
+ for (var i = 0; i < Path.Count; i++)
+ {
+ var segment = Path[i];
+ PathSegment newSegment;
+
+ if (segment.PropertyName is not null)
+ {
+ // PropertyName segments are (currently) constants, nothing to visit.
+ newSegment = segment;
+ }
+ else
+ {
+ var newArrayIndex = (SqlExpression)visitor.Visit(segment.ArrayIndex)!;
+ if (newArrayIndex == segment.ArrayIndex)
+ {
+ newSegment = segment;
+ }
+ else
+ {
+ newSegment = new PathSegment(newArrayIndex);
+
+ if (visitedPath is null)
+ {
+ visitedPath = new PathSegment[Path.Count];
+ for (var j = 0; j < i; i++)
+ {
+ visitedPath[j] = Path[j];
+ }
+ }
+ }
+ }
+
+ if (visitedPath is not null)
+ {
+ visitedPath[i] = newSegment;
+ }
+ }
+ }
+
+ return Update(visitedJsonExpression, visitedPath ?? Path, ColumnInfos);
+ }
+
+ public override TableValuedFunctionExpression Update(IReadOnlyList arguments)
+ => Update(arguments[0], Path, ColumnInfos);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual SingleStoreJsonTableExpression Update(
+ SqlExpression jsonExpression,
+ IReadOnlyList path,
+ IReadOnlyList columnInfos = null)
+ => Equals(jsonExpression, JsonExpression)
+ && (ReferenceEquals(path, Path) || path is not null && Path is not null && path.SequenceEqual(Path))
+ && (ReferenceEquals(columnInfos, ColumnInfos) || columnInfos is not null && ColumnInfos is not null && columnInfos.SequenceEqual(ColumnInfos))
+ ? this
+ : new SingleStoreJsonTableExpression(Alias, jsonExpression, path, columnInfos);
+
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ // TODO: Deep clone, see #30982
+ public virtual TableExpressionBase Clone()
+ {
+ var clone = new SingleStoreJsonTableExpression(Alias, JsonExpression, Path, ColumnInfos);
+
+ foreach (var annotation in GetAnnotations())
+ {
+ clone.AddAnnotation(annotation.Name, annotation.Value);
+ }
+
+ return clone;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override void Print(ExpressionPrinter expressionPrinter)
+ {
+ expressionPrinter.Append(Name);
+ expressionPrinter.Append("(");
+ expressionPrinter.Visit(JsonExpression);
+
+ var path = Path ?? Array.Empty();
+
+ expressionPrinter
+ .Append(", '$")
+ .Append(string.Join(".", path.Select(e => e.ToString())))
+ .Append("'");
+
+ if (ColumnInfos is not null)
+ {
+ expressionPrinter.Append(" COLUMNS (");
+
+ for (var i = 0; i < ColumnInfos.Count; i++)
+ {
+ var columnInfo = ColumnInfos[i];
+
+ if (i > 0)
+ {
+ expressionPrinter.Append(", ");
+ }
+
+ expressionPrinter
+ .Append(columnInfo.Name)
+ .Append(" ")
+ .Append(columnInfo.TypeMapping.StoreType);
+
+ if (columnInfo.Path is not null)
+ {
+ expressionPrinter
+ .Append(" PATH '")
+ .Append(string.Join(".", columnInfo.Path.Select(e => e.ToString())))
+ .Append("'");
+ }
+
+ if (columnInfo.AsJson)
+ {
+ expressionPrinter.Append(" AS JSON");
+ }
+ }
+
+ expressionPrinter.Append(")");
+ }
+
+ expressionPrinter.Append(")");
+
+ PrintAnnotations(expressionPrinter);
+
+ expressionPrinter.Append(" AS ");
+ expressionPrinter.Append(Alias);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ => ReferenceEquals(this, obj) || (obj is SingleStoreJsonTableExpression jsonTableExpression && Equals(jsonTableExpression));
+
+ private bool Equals(SingleStoreJsonTableExpression other)
+ {
+ if (!base.Equals(other) || ColumnInfos?.Count != other.ColumnInfos?.Count)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(ColumnInfos, other.ColumnInfos))
+ {
+ return true;
+ }
+
+ for (var i = 0; i < ColumnInfos!.Count; i++)
+ {
+ var (columnInfo, otherColumnInfo) = (ColumnInfos[i], other.ColumnInfos![i]);
+
+ if (columnInfo.Name != otherColumnInfo.Name
+ || !columnInfo.TypeMapping.Equals(otherColumnInfo.TypeMapping)
+ || (columnInfo.Path is null != otherColumnInfo.Path is null
+ || (columnInfo.Path is not null
+ && otherColumnInfo.Path is not null
+ && columnInfo.Path.SequenceEqual(otherColumnInfo.Path))))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public readonly record struct ColumnInfo(
+ string Name,
+ RelationalTypeMapping TypeMapping,
+ IReadOnlyList Path = null,
+ bool AsJson = false);
+}
diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs
index f11f8ab72..ebf5c15ef 100644
--- a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs
@@ -4,20 +4,22 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Internal;
using EntityFrameworkCore.SingleStore.Query.Internal;
namespace EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal
{
- public class SingleStoreStringComparisonMethodTranslator : IMethodCallTranslator
+ public class SingleStoreStringComparisonMethodTranslator : SingleStoreQueryCompilationContextMethodTranslator
{
private static readonly MethodInfo _equalsMethodInfo
= typeof(string).GetRuntimeMethod(nameof(string.Equals), new[] {typeof(string), typeof(StringComparison)});
@@ -57,24 +59,35 @@ private static readonly MethodInfo _indexOfMethodInfoWithStartIndexArg
_staticEqualsMethodInfo,
};
- private readonly SqlExpression _caseSensitiveComparisons;
+ private readonly IReadOnlyList _caseSensitiveComparisons;
private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory;
private readonly ISingleStoreOptions _options;
- public SingleStoreStringComparisonMethodTranslator(ISqlExpressionFactory sqlExpressionFactory, ISingleStoreOptions options)
+ private static readonly MethodInfo _escapeLikePatternParameterMethod =
+ typeof(SingleStoreStringComparisonMethodTranslator).GetTypeInfo().GetDeclaredMethod(nameof(ConstructLikePatternParameter))!;
+
+ public SingleStoreStringComparisonMethodTranslator(
+ ISqlExpressionFactory sqlExpressionFactory,
+ Func queryCompilationContextResolver,
+ ISingleStoreOptions options)
+ : base(queryCompilationContextResolver)
{
_sqlExpressionFactory = (SingleStoreSqlExpressionFactory)sqlExpressionFactory;
_options = options;
- _caseSensitiveComparisons = _sqlExpressionFactory.Constant(
- new[] {StringComparison.Ordinal, StringComparison.CurrentCulture, StringComparison.InvariantCulture});
+ _caseSensitiveComparisons = new[]
+ {
+ _sqlExpressionFactory.Constant(StringComparison.Ordinal),
+ _sqlExpressionFactory.Constant(StringComparison.CurrentCulture),
+ _sqlExpressionFactory.Constant(StringComparison.InvariantCulture)
+ }.ToList().AsReadOnly();
}
- public virtual SqlExpression Translate(
+ public override SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList arguments,
- IDiagnosticsLogger logger)
+ QueryCompilationContext queryCompilationContext)
{
if(_options.StringComparisonTranslations)
{
@@ -91,29 +104,17 @@ public virtual SqlExpression Translate(
if (Equals(method, _startsWithMethodInfo))
{
- return MakeStartsWithExpression(
- instance,
- arguments[0],
- arguments[1]
- );
+ return MakeStartsWithExpression(queryCompilationContext, instance, arguments[0], arguments[1]);
}
if (Equals(method, _endsWithMethodInfo))
{
- return MakeEndsWithExpression(
- instance,
- arguments[0],
- arguments[1]
- );
+ return MakeEndsWithExpression(queryCompilationContext, instance, arguments[0], arguments[1]);
}
if (Equals(method, _containsMethodInfo))
{
- return MakeContainsExpression(
- instance,
- arguments[0],
- arguments[1]
- );
+ return MakeContainsExpression(queryCompilationContext, instance, arguments[0], arguments[1]);
}
if (Equals(method, _indexOfMethodInfo))
@@ -182,7 +183,7 @@ public virtual SqlExpression MakeStringEqualsExpression(
new[]
{
new CaseWhenClause(
- _sqlExpressionFactory.In(stringComparison, _caseSensitiveComparisons, false),
+ _sqlExpressionFactory.In(stringComparison, _caseSensitiveComparisons),
// Case sensitive, accent sensitive
_sqlExpressionFactory.Equal(
leftValue,
@@ -198,6 +199,7 @@ public virtual SqlExpression MakeStringEqualsExpression(
}
public virtual SqlExpression MakeStartsWithExpression(
+ QueryCompilationContext queryCompilationContext,
[NotNull] SqlExpression target,
[NotNull] SqlExpression prefix,
[CanBeNull] SqlExpression stringComparison = null)
@@ -205,6 +207,7 @@ public virtual SqlExpression MakeStartsWithExpression(
if (stringComparison == null)
{
return MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => e,
prefix,
@@ -217,12 +220,14 @@ public virtual SqlExpression MakeStartsWithExpression(
return CreateExpressionForCaseSensitivity(
cmp,
() => MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => e,
prefix,
e => Utf8Bin(e),
true),
() => MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
LCase(target),
e => LCase(e),
prefix,
@@ -236,10 +241,10 @@ public virtual SqlExpression MakeStartsWithExpression(
new CaseWhenClause(
_sqlExpressionFactory.In(
stringComparison,
- _caseSensitiveComparisons,
- false),
+ _caseSensitiveComparisons),
// Case sensitive, accent sensitive
MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => e,
prefix,
@@ -248,6 +253,7 @@ public virtual SqlExpression MakeStartsWithExpression(
},
// Case insensitive, accent sensitive
MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => LCase(e),
prefix,
@@ -256,6 +262,7 @@ public virtual SqlExpression MakeStartsWithExpression(
}
public virtual SqlExpression MakeEndsWithExpression(
+ QueryCompilationContext queryCompilationContext,
[NotNull] SqlExpression target,
[NotNull] SqlExpression suffix,
[CanBeNull] SqlExpression stringComparison = null)
@@ -263,6 +270,7 @@ public virtual SqlExpression MakeEndsWithExpression(
if (stringComparison == null)
{
return MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => e,
suffix,
@@ -275,12 +283,14 @@ public virtual SqlExpression MakeEndsWithExpression(
return CreateExpressionForCaseSensitivity(
cmp,
() => MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => e,
suffix,
e => Utf8Bin(e),
false),
() => MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => LCase(e),
suffix,
@@ -294,10 +304,10 @@ public virtual SqlExpression MakeEndsWithExpression(
new CaseWhenClause(
_sqlExpressionFactory.In(
stringComparison,
- _caseSensitiveComparisons,
- false),
+ _caseSensitiveComparisons),
// Case sensitive, accent sensitive
MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => e,
suffix,
@@ -306,6 +316,7 @@ public virtual SqlExpression MakeEndsWithExpression(
},
// Case insensitive, accent sensitive
MakeStartsWithEndsWithExpressionImpl(
+ queryCompilationContext,
target,
e => LCase(e),
suffix,
@@ -314,6 +325,7 @@ public virtual SqlExpression MakeEndsWithExpression(
}
public virtual SqlExpression MakeContainsExpression(
+ QueryCompilationContext queryCompilationContext,
[NotNull] SqlExpression target,
[NotNull] SqlExpression search,
[CanBeNull] SqlExpression stringComparison = null)
@@ -323,6 +335,7 @@ public virtual SqlExpression MakeContainsExpression(
if (stringComparison == null)
{
return MakeContainsExpressionImpl(
+ queryCompilationContext,
target,
e => e,
search,
@@ -335,6 +348,7 @@ public virtual SqlExpression MakeContainsExpression(
cmp,
() =>
MakeContainsExpressionImpl(
+ queryCompilationContext,
target,
e => e,
search,
@@ -342,6 +356,7 @@ public virtual SqlExpression MakeContainsExpression(
),
() =>
MakeContainsExpressionImpl(
+ queryCompilationContext,
target,
e => LCase(e),
search,
@@ -354,9 +369,10 @@ public virtual SqlExpression MakeContainsExpression(
new[]
{
new CaseWhenClause(
- _sqlExpressionFactory.In(stringComparison, _caseSensitiveComparisons, false),
+ _sqlExpressionFactory.In(stringComparison, _caseSensitiveComparisons),
// Case sensitive, accent sensitive
MakeContainsExpressionImpl(
+ queryCompilationContext,
target,
e => e,
search,
@@ -366,6 +382,7 @@ public virtual SqlExpression MakeContainsExpression(
},
// Case insensitive, accent sensitive
MakeContainsExpressionImpl(
+ queryCompilationContext,
target,
e => LCase(e),
search,
@@ -375,6 +392,7 @@ public virtual SqlExpression MakeContainsExpression(
}
private SqlExpression MakeStartsWithEndsWithExpressionImpl(
+ QueryCompilationContext queryCompilationContext,
SqlExpression target,
[NotNull] Func targetTransform,
SqlExpression prefixSuffix,
@@ -389,57 +407,54 @@ private SqlExpression MakeStartsWithEndsWithExpressionImpl(
{
// The prefix is constant. Aside from null or empty, we escape all special characters (%, _, \)
// in C# and send a simple LIKE.
- if (constantPrefixSuffixExpression.Value is string constantPrefixSuffixString)
+ return constantPrefixSuffixExpression.Value switch
{
- // TRUE (pattern == "")
- // something LIKE 'foo%' (pattern != "", StartsWith())
- // something LIKE '%foo' (pattern != "", EndsWith())
- return constantPrefixSuffixString == string.Empty
- ? (SqlExpression)_sqlExpressionFactory.Constant(true)
- : _sqlExpressionFactory.Like(
- targetTransform(target),
- prefixSuffixTransform(
- _sqlExpressionFactory.Constant(
- (startsWith
- ? string.Empty
- : "%") +
- EscapeLikePattern(constantPrefixSuffixString) +
- (startsWith
- ? "%"
- : string.Empty))));
- }
+ null => _sqlExpressionFactory.Like(targetTransform(target), _sqlExpressionFactory.Constant(null, stringTypeMapping)),
+ "" => _sqlExpressionFactory.Like(targetTransform(target), _sqlExpressionFactory.Constant("%")),
+ string s => _sqlExpressionFactory.Like(
+ targetTransform(target),
+ prefixSuffixTransform(
+ _sqlExpressionFactory.Constant(
+ $"{(startsWith ? string.Empty : "%")}{(s.Any(IsLikeWildChar) ? EscapeLikePattern(s) : s)}{(startsWith ? "%" : string.Empty)}"))),
+ _ => throw new UnreachableException(),
+ };
+ }
- // TODO: EF Core 5
- // https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/996#issuecomment-607876040
- // Can return NULL in .NET 5 after https://github.com/dotnet/efcore/issues/20498 has been fixed.
- // `something LIKE NULL` always returns `NULL`. We will return `false`, to indicate, that no match
- // could be found, because returning a constant of `NULL` will throw later in EF Core when used as
- // a predicate.
- // return _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping);
- // This results in NULL anyway, but works around EF Core's inability to handle predicates that are
- // constant null values.
- return _sqlExpressionFactory.Like(target, _sqlExpressionFactory.Constant(null, stringTypeMapping));
+ if (GetLikeExpressionUsingParameter(
+ queryCompilationContext,
+ target,
+ targetTransform,
+ prefixSuffix,
+ stringTypeMapping,
+ startsWith
+ ? StartsEndsWithContains.StartsWith
+ : StartsEndsWithContains.EndsWith) is { } likeExpressionUsingParameter)
+ {
+ return likeExpressionUsingParameter;
}
// TODO: Generally, LEFT & compare is faster than escaping potential pattern characters with REPLACE().
// However, this might not be the case, if the pattern is constant after all (e.g. `LCASE('fo%o')`), in
// which case, `something LIKE CONCAT(REPLACE(REPLACE(LCASE('fo%o'), '%', '\\%'), '_', '\\_'), '%')` should
// be faster than `LEFT(something, CHAR_LENGTH('fo%o')) = LCASE('fo%o')`.
- // See https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/996#issuecomment-607733553
-
- // The prefix is non-constant, we use LEFT to extract the substring and compare.
- return _sqlExpressionFactory.Equal(
- _sqlExpressionFactory.NullableFunction(
- startsWith
- ? "LEFT"
- : "RIGHT",
- new[] {targetTransform(target), CharLength(prefixSuffix)},
- typeof(string),
- stringTypeMapping),
- prefixSuffixTransform(prefixSuffix));
+ // See https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/996#issuecomment-607733553
+
+ // The prefix is non-constant, we use LEFT/RIGHT to extract the substring and compare.
+ return _sqlExpressionFactory.AndAlso(
+ _sqlExpressionFactory.IsNotNull(targetTransform(target)),
+ _sqlExpressionFactory.AndAlso(
+ _sqlExpressionFactory.IsNotNull(prefixSuffix),
+ _sqlExpressionFactory.Equal(
+ _sqlExpressionFactory.NullableFunction(
+ startsWith ? "LEFT" : "RIGHT",
+ new[] { targetTransform(target), CharLength(prefixSuffix), },
+ typeof(string),
+ stringTypeMapping),
+ prefixSuffixTransform(prefixSuffix))));
}
private SqlExpression MakeContainsExpressionImpl(
+ QueryCompilationContext queryCompilationContext,
SqlExpression target,
[NotNull] Func targetTransform,
SqlExpression pattern,
@@ -453,41 +468,86 @@ private SqlExpression MakeContainsExpressionImpl(
{
// The prefix is constant. Aside from null or empty, we escape all special characters (%, _, \)
// in C# and send a simple LIKE.
- if (constantPatternExpression.Value is string constantPatternString)
+ // The prefix is constant. Aside from null or empty, we escape all special characters (%, _, \)
+ // in C# and send a simple LIKE.
+ return constantPatternExpression.Value switch
{
- // TRUE (pattern == "")
- // something LIKE '%foo%' (pattern != "")
- return constantPatternString == string.Empty
- ? (SqlExpression)_sqlExpressionFactory.Constant(true)
- : _sqlExpressionFactory.Like(
- targetTransform(target),
- patternTransform(_sqlExpressionFactory.Constant('%' + EscapeLikePattern(constantPatternString) + '%')));
- }
+ null => _sqlExpressionFactory.Like(targetTransform(target), _sqlExpressionFactory.Constant(null, stringTypeMapping)),
+ "" => _sqlExpressionFactory.Like(targetTransform(target), _sqlExpressionFactory.Constant("%")),
+ string s => _sqlExpressionFactory.Like(
+ targetTransform(target),
+ patternTransform(_sqlExpressionFactory.Constant($"%{(s.Any(IsLikeWildChar) ? EscapeLikePattern(s) : s)}%"))),
+ _ => throw new UnreachableException(),
+ };
+ }
- // TODO: EF Core 5
- // https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/996#issuecomment-607876040
- // Can return NULL in .NET 5 after https://github.com/dotnet/efcore/issues/20498 has been fixed.
- // `something LIKE NULL` always returns `NULL`. We will return `false`, to indicate, that no match
- // could be found, because returning a constant of `NULL` will throw later in EF Core when used as
- // a predicate.
- // return _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping);
- // This results in NULL anyway, but works around EF Core's inability to handle predicates that are
- // constant null values.
- return _sqlExpressionFactory.Like(target, _sqlExpressionFactory.Constant(null, stringTypeMapping));
+ if (GetLikeExpressionUsingParameter(
+ queryCompilationContext,
+ target,
+ targetTransform,
+ pattern,
+ stringTypeMapping,
+ StartsEndsWithContains.Contains) is { } likeExpressionUsingParameter)
+ {
+ return likeExpressionUsingParameter;
}
// 'foo' LIKE '' OR LOCATE('foo', 'barfoobar') > 0
// This cannot be "' ' = '' OR ..", because ' ' would be trimmed to '' when using equals, but not when using LIKE.
// Using an empty pattern `LOCATE('', 'barfoobar')` returns 1.
- return _sqlExpressionFactory.OrElse(
- _sqlExpressionFactory.Like(
- pattern,
- _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
- _sqlExpressionFactory.GreaterThan(
- Locate(patternTransform(pattern), targetTransform(target)),
- _sqlExpressionFactory.Constant(0)));
+ // return _sqlExpressionFactory.OrElse(
+ // _sqlExpressionFactory.Like(
+ // pattern,
+ // _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
+ // _sqlExpressionFactory.GreaterThan(
+ // Locate(patternTransform(pattern), targetTransform(target)),
+ // _sqlExpressionFactory.Constant(0)));
+
+ // For Contains, just use CHARINDEX and check if the result is greater than 0.
+ // Add a check to return null when the pattern is an empty string (and the string isn't null)
+ return _sqlExpressionFactory.AndAlso(
+ _sqlExpressionFactory.IsNotNull(target),
+ _sqlExpressionFactory.AndAlso(
+ _sqlExpressionFactory.IsNotNull(pattern),
+ _sqlExpressionFactory.OrElse(
+ _sqlExpressionFactory.GreaterThan(
+ Locate(patternTransform(pattern), targetTransform(target)),
+ _sqlExpressionFactory.Constant(0)),
+ _sqlExpressionFactory.Like(
+ pattern,
+ _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)))));
}
+ protected virtual SqlExpression GetLikeExpressionUsingParameter(QueryCompilationContext queryCompilationContext,
+ SqlExpression target,
+ Func targetTransform,
+ SqlExpression pattern,
+ RelationalTypeMapping stringTypeMapping,
+ StartsEndsWithContains methodType)
+ {
+ if (pattern is SqlParameterExpression patternParameter &&
+ patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal))
+ {
+ // The pattern is a parameter, register a runtime parameter that will contain the rewritten LIKE pattern, where
+ // all special characters have been escaped.
+ var lambda = Expression.Lambda(
+ Expression.Call(
+ _escapeLikePatternParameterMethod,
+ QueryCompilationContext.QueryContextParameter,
+ Expression.Constant(patternParameter.Name),
+ Expression.Constant(methodType)),
+ QueryCompilationContext.QueryContextParameter);
+
+ var escapedPatternParameter =
+ queryCompilationContext.RegisterRuntimeParameter(patternParameter.Name + "_rewritten", lambda);
+
+ return _sqlExpressionFactory.Like(
+ targetTransform(target),
+ new SqlParameterExpression(escapedPatternParameter.Name!, escapedPatternParameter.Type, stringTypeMapping));
+ }
+
+ return null;
+ }
public virtual SqlExpression MakeIndexOfExpression(
[NotNull] SqlExpression target,
[NotNull] SqlExpression search,
@@ -534,8 +594,7 @@ public virtual SqlExpression MakeIndexOfExpression(
new CaseWhenClause(
_sqlExpressionFactory.In(
stringComparison,
- _caseSensitiveComparisons,
- false),
+ _caseSensitiveComparisons),
// Case sensitive, accent sensitive
MakeIndexOfExpressionImpl(
target,
@@ -652,5 +711,46 @@ private static string EscapeLikePattern(string pattern)
return builder.ToString();
}
+
+ private static string ConstructLikePatternParameter(
+ QueryContext queryContext,
+ string baseParameterName,
+ StartsEndsWithContains methodType)
+ => queryContext.ParameterValues[baseParameterName] switch
+ {
+ null => null,
+
+ // In .NET, all strings start/end with the empty string, but SQL LIKE return false for empty patterns.
+ // Return % which always matches instead.
+ "" => "%",
+
+ string s => methodType switch
+ {
+ StartsEndsWithContains.StartsWith => EscapeLikePattern(s) + '%',
+ StartsEndsWithContains.EndsWith => '%' + EscapeLikePattern(s),
+ StartsEndsWithContains.Contains => $"%{EscapeLikePattern(s)}%",
+ _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null)
+ },
+
+ _ => throw new UnreachableException()
+ };
+
+ protected enum StartsEndsWithContains
+ {
+ ///
+ /// StartsWith => LIKE 'foo%'
+ ///
+ StartsWith,
+
+ ///
+ /// EndsWith => LIKE '%foo'
+ ///
+ EndsWith,
+
+ ///
+ /// Contains => LIKE '%foo%'
+ ///
+ Contains
+ }
}
}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBoolOptimizingExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBoolOptimizingExpressionVisitor.cs
index 49ffc4d49..6dd7ad7bb 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBoolOptimizingExpressionVisitor.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBoolOptimizingExpressionVisitor.cs
@@ -14,7 +14,7 @@ namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal
{
///
/// "WHERE `boolColumn`" doesn't use available indices, while "WHERE `boolColumn` = TRUE" does.
- /// See https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/1104
+ /// See https://github.com/PomeloFoundation/EntityFrameworkCore.MySql/issues/1104
///
public class SingleStoreBoolOptimizingExpressionVisitor : SqlExpressionVisitor
{
@@ -134,17 +134,41 @@ protected override Expression VisitFromSql(FromSqlExpression fromSqlExpression)
protected override Expression VisitIn(InExpression inExpression)
{
- Check.NotNull(inExpression, nameof(inExpression));
-
var parentOptimize = _optimize;
_optimize = false;
var item = (SqlExpression)Visit(inExpression.Item);
var subquery = (SelectExpression)Visit(inExpression.Subquery);
- var values = (SqlExpression)Visit(inExpression.Values);
+
+ var values = inExpression.Values;
+ SqlExpression[] newValues = null;
+ if (values is not null)
+ {
+ for (var i = 0; i < values.Count; i++)
+ {
+ var value = values[i];
+ var newValue = (SqlExpression)Visit(value);
+
+ if (newValue != value && newValues is null)
+ {
+ newValues = new SqlExpression[values.Count];
+ for (var j = 0; j < i; j++)
+ {
+ newValues[j] = values[j];
+ }
+ }
+
+ if (newValues is not null)
+ {
+ newValues[i] = newValue;
+ }
+ }
+ }
+
+ var valuesParameter = (SqlParameterExpression)Visit(inExpression.ValuesParameter);
_optimize = parentOptimize;
- return ApplyConversion(inExpression.Update(item, values, subquery), condition: true);
+ return ApplyConversion(inExpression.Update(item, subquery, newValues ?? values, valuesParameter), condition: true);
}
protected override Expression VisitLike(LikeExpression likeExpression)
@@ -517,6 +541,21 @@ protected override Expression VisitLeftJoin(LeftJoinExpression leftJoinExpressio
return leftJoinExpression.Update(table, joinPredicate);
}
+ protected override Expression VisitRowValue(RowValueExpression rowValueExpression)
+ {
+ var parentOptimize = _optimize;
+ _optimize = false;
+
+ var values = new SqlExpression[rowValueExpression.Values.Count];
+ for (var i = 0; i < values.Length; i++)
+ {
+ values[i] = (SqlExpression)Visit(rowValueExpression.Values[i]);
+ }
+
+ _optimize = parentOptimize;
+ return rowValueExpression.Update(values);
+ }
+
protected override Expression VisitScalarSubquery(ScalarSubqueryExpression scalarSubqueryExpression)
{
Check.NotNull(scalarSubqueryExpression, nameof(scalarSubqueryExpression));
@@ -626,6 +665,21 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression)
}
protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)
- => jsonScalarExpression;
+ => ApplyConversion(jsonScalarExpression, condition: false);
+
+ protected override Expression VisitValues(ValuesExpression valuesExpression)
+ {
+ var parentOptimize = _optimize;
+ _optimize = false;
+
+ var rowValues = new RowValueExpression[valuesExpression.RowValues.Count];
+ for (var i = 0; i < rowValues.Length; i++)
+ {
+ rowValues[i] = (RowValueExpression)Visit(valuesExpression.RowValues[i]);
+ }
+
+ _optimize = parentOptimize;
+ return valuesExpression.Update(rowValues);
+ }
}
}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBug96947WorkaroundExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBug96947WorkaroundExpressionVisitor.cs
index bb08574e7..9e77b0469 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBug96947WorkaroundExpressionVisitor.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreBug96947WorkaroundExpressionVisitor.cs
@@ -16,7 +16,7 @@ namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal
///
/// See https://bugs.mysql.com/bug.php?id=96947
/// https://github.com/OData/WebApi/issues/2124
- /// https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/1293
+ /// https://github.com/PomeloFoundation/EntityFrameworkCore.MySql/issues/1293
///
public class SingleStoreBug96947WorkaroundExpressionVisitor : ExpressionVisitor
{
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreCompatibilityExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreCompatibilityExpressionVisitor.cs
index a45907aff..663f3711d 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreCompatibilityExpressionVisitor.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreCompatibilityExpressionVisitor.cs
@@ -3,12 +3,15 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
+using EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal;
namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal
{
@@ -16,6 +19,11 @@ public class SingleStoreCompatibilityExpressionVisitor : ExpressionVisitor
{
private readonly ISingleStoreOptions _options;
+ private SelectExpression _currentSelectExpression;
+ private SelectExpression _parentSelectExpression;
+
+ private readonly SingleStoreContainsAggregateFunctionExpressionVisitor _mySqlContainsAggregateFunctionExpressionVisitor = new SingleStoreContainsAggregateFunctionExpressionVisitor();
+
public SingleStoreCompatibilityExpressionVisitor(ISingleStoreOptions options)
{
_options = options;
@@ -28,7 +36,12 @@ protected override Expression VisitExtension(Expression extensionExpression)
CrossApplyExpression crossApplyExpression => VisitCrossApply(crossApplyExpression),
OuterApplyExpression outerApplyExpression => VisitOuterApply(outerApplyExpression),
ExceptExpression exceptExpression => VisitExcept(exceptExpression),
- IntersectExpression intersectExpression => VisitIntercept(intersectExpression),
+ IntersectExpression intersectExpression => VisitIntersect(intersectExpression),
+ JsonScalarExpression jsonScalarExpression => VisitJsonScalar(jsonScalarExpression),
+ SingleStoreJsonTableExpression jsonTableExpression => VisitJsonTable(jsonTableExpression),
+
+ SelectExpression selectExpression => VisitSelect(selectExpression),
+
ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression)),
_ => base.VisitExtension(extensionExpression)
};
@@ -45,9 +58,75 @@ protected virtual Expression VisitOuterApply(OuterApplyExpression outerApplyExpr
protected virtual Expression VisitExcept(ExceptExpression exceptExpression)
=> CheckSupport(exceptExpression, _options.ServerVersion.Supports.ExceptIntercept);
- protected virtual Expression VisitIntercept(IntersectExpression intersectExpression)
+ protected virtual Expression VisitIntersect(IntersectExpression intersectExpression)
=> CheckSupport(intersectExpression, _options.ServerVersion.Supports.ExceptIntercept);
+ protected virtual Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)
+ => CheckSupport(jsonScalarExpression, _options.ServerVersion.Supports.Json);
+
+ protected virtual Expression VisitJsonTable(SingleStoreJsonTableExpression jsonTableExpression)
+ {
+ if (!_options.ServerVersion.Supports.JsonTable)
+ {
+ return CheckSupport(jsonTableExpression, false);
+ }
+
+ if (!_options.ServerVersion.Supports.JsonTableImplementationWithAggregate &&
+ _mySqlContainsAggregateFunctionExpressionVisitor.ProcessSelect(_currentSelectExpression))
+ {
+ throw new InvalidOperationException($"JSON_TABLE() does not support aggregates on {_options.ServerVersion} and would return unexpected results if used.");
+ }
+
+ if (!_options.ServerVersion.Supports.OuterApply &&
+ jsonTableExpression.JsonExpression is ColumnExpression columnExpression &&
+ _parentSelectExpression is not null &&
+ _parentSelectExpression.Tables.All(t => t.Alias != columnExpression.TableAlias))
+ {
+ throw new InvalidOperationException($"JSON_TABLE() does not support references to an outer query that is not the immediate parent on {_options.ServerVersion}.");
+ }
+
+ return jsonTableExpression;
+ }
+
+ protected virtual Expression VisitSelect(SelectExpression selectExpression)
+ {
+ var grandParentSelectExpression = _parentSelectExpression;
+ _parentSelectExpression = _currentSelectExpression;
+ _currentSelectExpression = selectExpression;
+
+ foreach (var item in selectExpression.Projection)
+ {
+ Visit(item);
+ }
+
+ foreach (var table in selectExpression.Tables)
+ {
+ Visit(table);
+ }
+
+ Visit(selectExpression.Predicate);
+
+ foreach (var groupingKey in selectExpression.GroupBy)
+ {
+ Visit(groupingKey);
+ }
+
+ Visit(selectExpression.Having);
+
+ foreach (var ordering in selectExpression.Orderings)
+ {
+ Visit(ordering.Expression);
+ }
+
+ Visit(selectExpression.Offset);
+ Visit(selectExpression.Limit);
+
+ _currentSelectExpression = _parentSelectExpression;
+ _parentSelectExpression = grandParentSelectExpression;
+
+ return selectExpression;
+ }
+
protected virtual Expression CheckSupport(Expression expression, bool isSupported)
=> CheckTranslated(
isSupported
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreContainsAggregateFunctionExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreContainsAggregateFunctionExpressionVisitor.cs
new file mode 100644
index 000000000..8c83680a8
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreContainsAggregateFunctionExpressionVisitor.cs
@@ -0,0 +1,114 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal;
+
+///
+/// Looks for aggregate functions (like SUM(), AVG() etc.) in an expression tree, but not in subqueries.
+///
+public sealed class SingleStoreContainsAggregateFunctionExpressionVisitor : ExpressionVisitor
+{
+ // See https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html
+ // See https://docs.singlestore.com/cloud/reference/sql-reference/aggregate-functions/
+ private static readonly SortedSet _aggregateFunctions = new SortedSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "ANY_VALUE",
+ "APPROX_PERCENTILE",
+ "AVG",
+ "BIT_AND",
+ "BIT_OR",
+ "BIT_XOR",
+ "COUNT",
+ "GROUP_CONCAT",
+ "MAX",
+ "MEDIAN",
+ "MIN",
+ "MOD",
+ "STD",
+ "STDDEV",
+ "STDDEV_POP",
+ "STDDEV_SAMP",
+ "SUM",
+ "VAR_SAMP",
+ "VARIANCE",
+ };
+
+ public bool AggregateFunctionFound { get; private set; }
+
+ public bool ProcessUntilSelect(Expression node)
+ {
+ // Can be reused within the same thread.
+ AggregateFunctionFound = false;
+
+ Visit(node);
+
+ return AggregateFunctionFound;
+ }
+
+ public bool ProcessSelect(SelectExpression selectExpression)
+ {
+ // Can be reused within the same thread.
+ AggregateFunctionFound = false;
+
+ foreach (var item in selectExpression.Projection)
+ {
+ Visit(item);
+ }
+
+ foreach (var table in selectExpression.Tables)
+ {
+ Visit(table);
+ }
+
+ Visit(selectExpression.Predicate);
+
+ foreach (var groupingKey in selectExpression.GroupBy)
+ {
+ Visit(groupingKey);
+ }
+
+ Visit(selectExpression.Having);
+
+ foreach (var ordering in selectExpression.Orderings)
+ {
+ Visit(ordering.Expression);
+ }
+
+ Visit(selectExpression.Offset);
+ Visit(selectExpression.Limit);
+
+ return AggregateFunctionFound;
+ }
+
+ public override Expression Visit(Expression node)
+ => AggregateFunctionFound
+ ? node
+ : base.Visit(node);
+
+ protected override Expression VisitExtension(Expression extensionExpression)
+ => extensionExpression switch
+ {
+ SqlFunctionExpression sqlFunctionExpression => VisitSqlFunction(sqlFunctionExpression),
+ SelectExpression selectExpression => selectExpression,
+ ShapedQueryExpression shapedQueryExpression => shapedQueryExpression,
+ _ => base.VisitExtension(extensionExpression)
+ };
+
+ private Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExpression)
+ {
+ if (_aggregateFunctions.Contains(sqlFunctionExpression.Name))
+ {
+ AggregateFunctionFound = true;
+ return sqlFunctionExpression;
+ }
+
+ return base.VisitExtension(sqlFunctionExpression);
+ }
+}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreHavingExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreHavingExpressionVisitor.cs
index 0d77fbc2c..df237865d 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreHavingExpressionVisitor.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreHavingExpressionVisitor.cs
@@ -2,8 +2,6 @@
// Copyright (c) SingleStore Inc. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
-using System;
-using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
@@ -44,7 +42,7 @@ havingExpression is not SqlConstantExpression &&
havingExpression is not SingleStoreColumnAliasReferenceExpression)
{
_containsAggregateFunctionExpressionVisitor ??= new SingleStoreContainsAggregateFunctionExpressionVisitor();
- if (!_containsAggregateFunctionExpressionVisitor.Process(havingExpression))
+ if (!_containsAggregateFunctionExpressionVisitor.ProcessUntilSelect(havingExpression))
{
selectExpression.PushdownIntoSubquery();
var subQuery = (SelectExpression) selectExpression.Tables.Single();
@@ -87,71 +85,5 @@ havingExpression is not SqlConstantExpression &&
return base.VisitExtension(selectExpression);
}
-
- ///
- /// Looks for aggregate functions (like SUM(), AVG() etc.) in an expression tree, but not in subqueries.
- ///
- private sealed class SingleStoreContainsAggregateFunctionExpressionVisitor : ExpressionVisitor
- {
- // See https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html
- private static readonly SortedSet _aggregateFunctions = new SortedSet(StringComparer.OrdinalIgnoreCase)
- {
- "AVG",
- "BIT_AND",
- "BIT_OR",
- "BIT_XOR",
- "COUNT",
- "GROUP_CONCAT",
- "JSON_ARRAYAGG",
- "JSON_OBJECTAGG",
- "MAX",
- "MIN",
- "STD",
- "STDDEV",
- "STDDEV_POP",
- "STDDEV_SAMP",
- "SUM",
- "VAR_POP",
- "VAR_SAMP",
- "VARIANCE",
- };
-
- public bool AggregateFunctionFound { get; private set; }
-
- public bool Process(Expression node)
- {
- // Can be reused within the same thread.
- AggregateFunctionFound = false;
-
- Visit(node);
-
- return AggregateFunctionFound;
- }
-
- public override Expression Visit(Expression node)
- => AggregateFunctionFound
- ? node
- : base.Visit(node);
-
- protected override Expression VisitExtension(Expression extensionExpression)
- => extensionExpression switch
- {
- SqlFunctionExpression sqlFunctionExpression => VisitSqlFunction(sqlFunctionExpression),
- SelectExpression selectExpression => selectExpression,
- ShapedQueryExpression shapedQueryExpression => shapedQueryExpression,
- _ => base.VisitExtension(extensionExpression)
- };
-
- private Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExpression)
- {
- if (_aggregateFunctions.Contains(sqlFunctionExpression.Name))
- {
- AggregateFunctionFound = true;
- return sqlFunctionExpression;
- }
-
- return base.VisitExtension(sqlFunctionExpression);
- }
- }
}
}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreParameterInliningExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreParameterInliningExpressionVisitor.cs
new file mode 100644
index 000000000..df5af5dcd
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreParameterInliningExpressionVisitor.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Utilities;
+using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
+using EntityFrameworkCore.SingleStore.Query.Expressions.Internal;
+using EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal;
+
+namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal;
+
+///
+/// Inject parameter inlining expressions where parameters are not supported for some reason.
+///
+public class SingleStoreParameterInliningExpressionVisitor : ExpressionVisitor
+{
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly ISingleStoreOptions _options;
+
+ private IReadOnlyDictionary _parametersValues;
+ private bool _canCache;
+
+ private bool _inJsonTableSourceParameterCall;
+
+ public SingleStoreParameterInliningExpressionVisitor(
+ IRelationalTypeMappingSource typeMappingSource,
+ ISqlExpressionFactory sqlExpressionFactory,
+ ISingleStoreOptions options)
+ {
+ _typeMappingSource = typeMappingSource;
+ _sqlExpressionFactory = sqlExpressionFactory;
+ _options = options;
+ }
+
+ public virtual Expression Process(Expression expression, IReadOnlyDictionary parametersValues, out bool canCache)
+ {
+ Check.NotNull(expression, nameof(expression));
+
+ _parametersValues = parametersValues;
+ _canCache = true;
+
+ var result = Visit(expression);
+
+ canCache = _canCache;
+
+ return result;
+ }
+
+ protected override Expression VisitExtension(Expression extensionExpression)
+ => extensionExpression switch
+ {
+ SingleStoreJsonTableExpression jsonTableExpression => VisitJsonTable(jsonTableExpression),
+ SqlParameterExpression sqlParameterExpression => VisitSqlParameter(sqlParameterExpression),
+ ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(
+ Visit(shapedQueryExpression.QueryExpression),
+ Visit(shapedQueryExpression.ShaperExpression)),
+ _ => base.VisitExtension(extensionExpression)
+ };
+
+ protected virtual Expression VisitJsonTable(SingleStoreJsonTableExpression jsonTableExpression)
+ {
+ var parentInJsonTableSourceParameterCall = _inJsonTableSourceParameterCall;
+ _inJsonTableSourceParameterCall = true;
+ var jsonExpression = (SqlExpression)Visit(jsonTableExpression.JsonExpression);
+ _inJsonTableSourceParameterCall = parentInJsonTableSourceParameterCall;
+
+ return jsonTableExpression.Update(
+ jsonExpression,
+ jsonTableExpression.Path,
+ jsonTableExpression.ColumnInfos);
+ }
+
+ protected virtual Expression VisitSqlParameter(SqlParameterExpression sqlParameterExpression)
+ {
+ // For test simplicity, we currently inline parameters even for non MySQL database engines (even though it should not be necessary
+ // for e.g. MariaDB).
+ // TODO: Use inlined parameters only if JsonTableImplementationUsingParameterAsSourceWithoutEngineCrash is true.
+ if (_inJsonTableSourceParameterCall /*&&
+ !_options.ServerVersion.Supports.JsonTableImplementationUsingParameterAsSourceWithoutEngineCrash*/)
+ {
+ _canCache = false;
+
+ return new SingleStoreInlinedParameterExpression(
+ sqlParameterExpression,
+ _sqlExpressionFactory.Constant(
+ _parametersValues[sqlParameterExpression.Name],
+ sqlParameterExpression.TypeMapping));
+ }
+
+ return sqlParameterExpression;
+ }
+}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs
index e0b2de808..481beb65a 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
@@ -15,6 +16,7 @@
using Microsoft.EntityFrameworkCore.Utilities;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Query.Expressions.Internal;
+using EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal;
using EntityFrameworkCore.SingleStore.Query.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@@ -36,11 +38,14 @@ public class SingleStoreQuerySqlGenerator : QuerySqlGenerator
{ "float", new []{ "float" } },
{ "binary", new []{ "binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob" } },
{ "datetime(6)", new []{ "datetime(6)" } },
+ { "datetime(3)", new []{ "datetime(3)" } },
{ "datetime", new []{ "datetime" } },
{ "date", new []{ "date" } },
{ "timestamp(6)", new []{ "timestamp(6)" } },
+ { "timestamp(3)", new []{ "timestamp(3)" } },
{ "timestamp", new []{ "timestamp" } },
{ "time(6)", new []{ "time(6)" } },
+ { "time(3)", new []{ "time(3)" } },
{ "time", new []{ "time" } },
{ "json", new []{ "json" } },
{ "char", new []{ "char", "varchar", "text", "tinytext", "mediumtext", "longtext" } },
@@ -49,6 +54,7 @@ public class SingleStoreQuerySqlGenerator : QuerySqlGenerator
private const ulong LimitUpperBound = 18446744073709551610;
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly ISingleStoreOptions _options;
private string _removeTableAliasOld;
private string _removeTableAliasNew;
@@ -59,9 +65,11 @@ public class SingleStoreQuerySqlGenerator : QuerySqlGenerator
///
public SingleStoreQuerySqlGenerator(
[NotNull] QuerySqlGeneratorDependencies dependencies,
+ IRelationalTypeMappingSource typeMappingSource,
[CanBeNull] ISingleStoreOptions options)
: base(dependencies)
{
+ _typeMappingSource = typeMappingSource;
_options = options;
}
@@ -70,6 +78,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
SingleStoreJsonTraversalExpression jsonTraversalExpression => VisitJsonPathTraversal(jsonTraversalExpression),
SingleStoreColumnAliasReferenceExpression columnAliasReferenceExpression => VisitColumnAliasReference(columnAliasReferenceExpression),
+ SingleStoreJsonTableExpression jsonTableExpression => VisitJsonTableExpression(jsonTableExpression),
+ SingleStoreInlinedParameterExpression inlinedParameterExpression => VisitInlinedParameterExpression(inlinedParameterExpression),
_ => base.VisitExtension(extensionExpression)
};
@@ -80,6 +90,13 @@ private Expression VisitColumnAliasReference(SingleStoreColumnAliasReferenceExpr
return columnAliasReferenceExpression;
}
+ private Expression VisitInlinedParameterExpression(SingleStoreInlinedParameterExpression inlinedParameterExpression)
+ {
+ Visit(inlinedParameterExpression.ValueExpression);
+
+ return inlinedParameterExpression;
+ }
+
protected virtual Expression VisitJsonPathTraversal(SingleStoreJsonTraversalExpression expression)
{
// If the path contains parameters, then the -> and ->> aliases are not supported by MySQL, because
@@ -257,7 +274,8 @@ protected override Expression VisitOuterApply(OuterApplyExpression outerApplyExp
{
Sql.Append("LEFT JOIN ");
- if (outerApplyExpression.Table is not TableExpression)
+ if (outerApplyExpression.Table is not TableExpression &&
+ outerApplyExpression.Table is not SingleStoreJsonTableExpression)
{
Sql.Append("LATERAL ");
}
@@ -319,7 +337,7 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
// EF uses unary Equal and NotEqual to represent is-null checking.
// These need to be surrounded with parenthesis in various cases (e.g. where TRUE = x IS NOT NULL).
- // See https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/1309
+ // See https://github.com/PomeloFoundation/EntityFrameworkCore.MySql/issues/1309
requiresBrackets = RequiresBrackets(sqlBinaryExpression.Right) ||
!requiresBrackets &&
sqlBinaryExpression.Right is SqlUnaryExpression sqlUnaryExpression &&
@@ -408,7 +426,31 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression)
&& selectExpression.Projection.Count == 0)
{
Sql.Append("UPDATE ");
- GenerateList(selectExpression.Tables, e => Visit(e), sql => sql.AppendLine());
+
+ if (selectExpression.Tables.Count > 1)
+ {
+ var tables = selectExpression.Tables;
+
+ if (selectExpression.Tables.All(t => !updateExpression.Table.Equals(t is JoinExpressionBase join ? join.Table : t)))
+ {
+ Visit(updateExpression.Table);
+ Sql.AppendLine(",");
+
+ if (tables[0] is not JoinExpressionBase)
+ {
+ tables = tables
+ .Skip(1)
+ .Prepend(new CrossJoinExpression(tables[0]))
+ .ToArray();
+ }
+ }
+
+ GenerateList(tables, e => Visit(e), sql => sql.AppendLine());
+ }
+ else
+ {
+ Visit(updateExpression.Table);
+ }
Sql.AppendLine().Append("SET ");
Visit(updateExpression.ColumnValueSetters[0].Column);
@@ -441,6 +483,144 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression)
RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate)));
}
+ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)
+ {
+ // TODO: Stop producing empty JsonScalarExpressions, #30768
+ var path = jsonScalarExpression.Path;
+ if (path.Count == 0)
+ {
+ return jsonScalarExpression;
+ }
+
+ var jsonPathNeedsConcat = JsonPathNeedsConcat(jsonScalarExpression.Path);
+ var useJsonValue = !jsonPathNeedsConcat && _options.ServerVersion.Supports.JsonValue;
+ var jsonFunctionName = useJsonValue ? "JSON_VALUE" : "JSON_UNQUOTE(JSON_EXTRACT";
+ string castStoreType = null;
+
+ // if (/*jsonScalarExpression.TypeMapping is SqlServerJsonTypeMapping
+ // ||*/ jsonScalarExpression.TypeMapping?.ElementTypeMapping is not null)
+ // {
+ // jsonFunctionName = "JSON_UNQUOTE(JSON_EXTRACT";
+ // }
+ // else
+ // {
+ // // JSON_VALUE returns varchar(512) by default (https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-value),
+ // // so we let it cast the result to the expected type using the RETURNING clause.
+ // // CHECK: - except if it's a string (since the cast interferes with indexes over the JSON property).
+ // // if (jsonScalarExpression.TypeMapping is not StringTypeMapping)
+ // // {
+ // castStoreType = GetCastStoreType(jsonScalarExpression.TypeMapping);
+ // // }
+ //
+ // jsonFunctionName = "JSON_VALUE";
+ // }
+
+ // if (jsonScalarExpression.TypeMapping?.ElementTypeMapping is null &&
+ // jsonScalarExpression.TypeMapping is not StringTypeMapping &&
+ // jsonPathNeedsConcat)
+ // {
+ castStoreType = GetCastStoreType(jsonScalarExpression.TypeMapping);
+ // }
+
+ if (castStoreType is not null)
+ {
+ Sql.Append("CAST(");
+ }
+
+ Sql.Append(jsonFunctionName);
+ Sql.Append("(");
+
+ Visit(jsonScalarExpression.Json);
+
+ Sql.Append(", ");
+ GenerateJsonPath(jsonScalarExpression.Path);
+ Sql.Append(useJsonValue ? ")" : "))");
+
+ if (castStoreType is not null)
+ {
+ Sql.Append(" AS ");
+ Sql.Append(castStoreType);
+ Sql.Append(")");
+ }
+
+ return jsonScalarExpression;
+ }
+
+ protected override void GenerateValues(ValuesExpression valuesExpression)
+ {
+ if (_options.ServerVersion.Supports.Values ||
+ _options.ServerVersion.Supports.ValuesWithRows)
+ {
+ base.GenerateValues(valuesExpression);
+ return;
+ }
+
+ var rowValues = valuesExpression.RowValues;
+
+ //
+ // Use backwards compatible SELECT statements:
+ //
+
+ Sql.Append("SELECT ");
+
+ Check.DebugAssert(rowValues.Count > 0, "rowValues.Count > 0");
+ var firstRowValues = rowValues[0].Values;
+ for (var i = 0; i < firstRowValues.Count; i++)
+ {
+ if (i > 0)
+ {
+ Sql.Append(", ");
+ }
+
+ Visit(firstRowValues[i]);
+
+ Sql
+ .Append(AliasSeparator)
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(valuesExpression.ColumnNames[i]));
+ }
+
+ if (rowValues.Count > 1)
+ {
+ for (var r = 1; r < rowValues.Count; r++)
+ {
+ Sql.Append(" UNION ALL SELECT ");
+ Visit(rowValues[r]);
+ }
+ }
+ }
+
+ protected override Expression VisitRowValue(RowValueExpression rowValueExpression)
+ {
+ if (_options.ServerVersion.Supports.Values)
+ {
+ return base.VisitRowValue(rowValueExpression);
+ }
+
+ if (_options.ServerVersion.Supports.ValuesWithRows)
+ {
+ Sql.Append("ROW");
+ return base.VisitRowValue(rowValueExpression);
+ }
+
+ //
+ // Columns for backwards compatible SELECT statement:
+ //
+
+ var values = rowValueExpression.Values;
+ var count = values.Count;
+ for (var i = 0; i < count; i++)
+ {
+ if (i > 0)
+ {
+ Sql.Append(", ");
+ }
+
+ Visit(values[i]);
+ }
+
+ return rowValueExpression;
+ }
+
protected virtual void GenerateList(
IReadOnlyList items,
Action generationAction,
@@ -685,6 +865,152 @@ public virtual Expression VisitSingleStoreBinaryExpression(SingleStoreBinaryExpr
return mySqlBinaryExpression;
}
+ protected virtual Expression VisitJsonTableExpression(SingleStoreJsonTableExpression jsonTableExpression)
+ {
+ // if (jsonTableExpression.ColumnInfos is not { Count: > 0 })
+ // {
+ // var hasStringElement = jsonTableExpression.JsonExpression.TypeMapping?.ElementTypeMapping?.ClrType == typeof(string);
+ //
+ // if (hasStringElement)
+ // {
+ // Sql.Append("JSON_UNQUOTE(");
+ // }
+ //
+ // Sql.Append("JSON_EXTRACT(");
+ // Visit(jsonTableExpression.JsonExpression);
+ // Sql.Append(", ");
+ // GenerateJsonPath(jsonTableExpression.Path);
+ // Sql.Append(")");
+ //
+ // if (hasStringElement)
+ // {
+ // Sql.Append(")");
+ // }
+ //
+ // return jsonTableExpression;
+ // }
+
+ Sql.Append("JSON_TABLE(");
+
+ Visit(jsonTableExpression.JsonExpression);
+
+ Sql.Append(", ");
+ GenerateJsonPath(jsonTableExpression.Path);
+
+ if (jsonTableExpression.ColumnInfos is not { Count: > 0 })
+ {
+ throw new InvalidOperationException("JSON_TABLE expression does not contain any columns.");
+ }
+
+ Sql.AppendLine(" COLUMNS (");
+
+ using (var _ = Sql.Indent())
+ {
+ Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("key"));
+ Sql.AppendLine(" FOR ORDINALITY,");
+
+ for (var i = 0; i < jsonTableExpression.ColumnInfos.Count; i++)
+ {
+ var columnInfo = jsonTableExpression.ColumnInfos[i];
+
+ if (i > 0)
+ {
+ Sql.AppendLine(",");
+ }
+
+ GenerateColumnInfo(columnInfo);
+ }
+
+ Sql.AppendLine();
+ }
+
+ Sql.Append(")");
+
+ void GenerateColumnInfo(SingleStoreJsonTableExpression.ColumnInfo columnInfo)
+ {
+ Sql
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(columnInfo.Name))
+ .Append(" ")
+ .Append(columnInfo.TypeMapping.StoreType);
+
+ if (columnInfo.Path is not null)
+ {
+ Sql.Append(" PATH ");
+ GenerateJsonPath(columnInfo.Path);
+ }
+
+ // if (columnInfo.AsJson)
+ // {
+ // Sql.Append(" AS ").Append("JSON");
+ // }
+ }
+
+ Sql.Append(")");
+ Sql.Append(AliasSeparator).Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(jsonTableExpression.Alias));
+
+ return jsonTableExpression;
+ }
+
+ protected virtual bool JsonPathNeedsConcat(IReadOnlyList path)
+ => path.Any(s => s.ArrayIndex is not null && s.ArrayIndex is not SqlConstantExpression);
+
+ protected virtual void GenerateJsonPath(IReadOnlyList path, bool? needsConcat = null)
+ {
+ path ??= Array.Empty();
+ needsConcat ??= JsonPathNeedsConcat(path);
+
+ if (needsConcat.Value)
+ {
+ Sql.Append("CONCAT(");
+ }
+
+ Sql.Append("'$");
+
+ foreach (var pathSegment in path)
+ {
+ switch (pathSegment)
+ {
+ case { PropertyName: string propertyName }:
+ Sql.Append(".").Append(propertyName);
+ break;
+
+ case { ArrayIndex: SqlExpression arrayIndex }:
+ Sql.Append("[");
+
+ if (arrayIndex is SqlConstantExpression)
+ {
+ Visit(arrayIndex);
+ }
+ else
+ {
+ Sql.Append("', ");
+
+ Visit(
+ new SqlUnaryExpression(
+ ExpressionType.Convert,
+ arrayIndex,
+ typeof(string),
+ _typeMappingSource.GetMapping(typeof(string))));
+
+ Sql.Append(", '");
+ }
+
+ Sql.Append("]");
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ Sql.Append("'");
+
+ if (needsConcat.Value)
+ {
+ Sql.Append(")");
+ }
+ }
+
///
protected override void CheckComposableSql(string sql)
{
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGeneratorFactory.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGeneratorFactory.cs
index 8299f407b..cd1caa3bc 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGeneratorFactory.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGeneratorFactory.cs
@@ -4,6 +4,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Storage;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal
@@ -11,17 +12,20 @@ namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal
public class SingleStoreQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
{
private readonly QuerySqlGeneratorDependencies _dependencies;
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly ISingleStoreOptions _options;
public SingleStoreQuerySqlGeneratorFactory(
[NotNull] QuerySqlGeneratorDependencies dependencies,
+ IRelationalTypeMappingSource typeMappingSource,
ISingleStoreOptions options)
{
_dependencies = dependencies;
+ _typeMappingSource = typeMappingSource;
_options = options;
}
public virtual QuerySqlGenerator Create()
- => new SingleStoreQuerySqlGenerator(_dependencies, _options);
+ => new SingleStoreQuerySqlGenerator(_dependencies, _typeMappingSource, _options);
}
}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryTranslationPreprocessor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryTranslationPreprocessor.cs
new file mode 100644
index 000000000..d7142646b
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryTranslationPreprocessor.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.Internal;
+
+namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal;
+
+public class SingleStoreQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
+{
+ private readonly RelationalQueryCompilationContext _relationalQueryCompilationContext;
+
+ public SingleStoreQueryTranslationPreprocessor(
+ QueryTranslationPreprocessorDependencies dependencies,
+ RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
+ QueryCompilationContext queryCompilationContext)
+ : base(dependencies, relationalDependencies, queryCompilationContext)
+ {
+ _relationalQueryCompilationContext = (RelationalQueryCompilationContext)queryCompilationContext;
+ }
+
+ ///
+ /// Workaround https://github.com/dotnet/efcore/issues/30386.
+ ///
+ public override Expression NormalizeQueryableMethod(Expression expression)
+ {
+ // Implementation of base (RelationalQueryTranslationPreprocessor).
+ expression = new RelationalQueryMetadataExtractingExpressionVisitor(_relationalQueryCompilationContext).Visit(expression);
+
+ // Implementation of base.base (QueryTranslationPreprocessor), using `SingleStoreQueryableMethodNormalizingExpressionVisitor` instead of
+ // `QueryableMethodNormalizingExpressionVisitor` directly.
+ expression = new SingleStoreQueryableMethodNormalizingExpressionVisitor(QueryCompilationContext).Normalize(expression);
+ expression = ProcessQueryRoots(expression);
+
+ return expression;
+ }
+}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryTranslationPreprocessorFactory.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryTranslationPreprocessorFactory.cs
new file mode 100644
index 000000000..81a96f488
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryTranslationPreprocessorFactory.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.Query;
+
+namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal;
+
+public class SingleStoreQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
+{
+ private readonly QueryTranslationPreprocessorDependencies _dependencies;
+ private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
+
+ public SingleStoreQueryTranslationPreprocessorFactory(
+ QueryTranslationPreprocessorDependencies dependencies,
+ RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
+ {
+ _dependencies = dependencies;
+ _relationalDependencies = relationalDependencies;
+ }
+
+ public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
+ => new SingleStoreQueryTranslationPreprocessor(
+ _dependencies,
+ _relationalDependencies,
+ queryCompilationContext);
+}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryableMethodNormalizingExpressionVisitor.cs
new file mode 100644
index 000000000..2c09739c3
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQueryableMethodNormalizingExpressionVisitor.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.Internal;
+using EntityFrameworkCore.SingleStore.Query.Expressions.Internal;
+
+namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal;
+
+///
+/// Skips normalization of array[index].Property to array.Select(e => e.Property).ElementAt(index),
+/// because it messes-up our JSON-Array handling in `SingleStoreSqlTranslatingExpressionVisitor`.
+/// See https://github.com/dotnet/efcore/issues/30386.
+///
+public class SingleStoreQueryableMethodNormalizingExpressionVisitor : QueryableMethodNormalizingExpressionVisitor
+{
+ public SingleStoreQueryableMethodNormalizingExpressionVisitor(QueryCompilationContext queryCompilationContext)
+ : base(queryCompilationContext)
+ {
+ }
+
+ protected override Expression VisitBinary(BinaryExpression binaryExpression)
+ {
+ // Convert array[x] to array.ElementAt(x)
+ if (binaryExpression is
+ {
+ NodeType: ExpressionType.ArrayIndex,
+ Left: var source,
+ Right: var index
+ })
+ {
+ return new SingleStoreBipolarExpression(
+ base.VisitBinary(binaryExpression),
+ binaryExpression.Update(
+ Visit(binaryExpression.Left),
+ VisitAndConvert(binaryExpression.Conversion, nameof(VisitBinary)),
+ Visit(binaryExpression.Right)));
+
+ // Original (base) implementation:
+ //
+ // return VisitMethodCall(
+ // Expression.Call(
+ // ElementAtMethodInfo.MakeGenericMethod(source.Type.GetSequenceType()), source, index));
+ }
+
+ return base.VisitBinary(binaryExpression);
+ }
+
+ protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
+ {
+ // Normalize list[x] to list.ElementAt(x)
+ if (methodCallExpression is
+ {
+ Method:
+ {
+ Name: "get_Item",
+ IsStatic: false,
+ DeclaringType: Type declaringType
+ },
+ Object: Expression indexerSource,
+ Arguments: [var index]
+ }
+ && declaringType.GetInterface("IReadOnlyList`1") is not null)
+ {
+ return new SingleStoreBipolarExpression(
+ base.VisitMethodCall(methodCallExpression),
+ methodCallExpression.Update(
+ Visit(methodCallExpression.Object),
+ VisitArguments(methodCallExpression.Arguments)));
+
+ IEnumerable VisitArguments(IEnumerable arguments)
+ {
+ foreach (var expression in arguments)
+ {
+ yield return VisitExtension(expression);
+ }
+ }
+
+ // Original (base) implementation:
+ //
+ // return VisitMethodCall(
+ // Expression.Call(
+ // ElementAtMethodInfo.MakeGenericMethod(indexerSource.Type.GetSequenceType()),
+ // indexerSource,
+ // index));
+ }
+
+ return base.VisitMethodCall(methodCallExpression);
+ }
+}
diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreSqlTranslatingExpressionVisitor.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreSqlTranslatingExpressionVisitor.cs
index 097a525e4..740c762c5 100644
--- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreSqlTranslatingExpressionVisitor.cs
@@ -3,6 +3,7 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
@@ -24,15 +25,27 @@ namespace EntityFrameworkCore.SingleStore.Query.ExpressionVisitors.Internal
{
public class SingleStoreSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExpressionVisitor
{
+ private readonly QueryCompilationContext _queryCompilationContext;
private readonly ISingleStoreJsonPocoTranslator _jsonPocoTranslator;
private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory;
protected static readonly MethodInfo[] NewArrayExpressionSupportMethodInfos = Array.Empty()
- .Concat(typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethods().Where(m => m.Name == nameof(SingleStoreDbFunctionsExtensions.Match)))
+ .Concat(typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethods().Where(m => m.Name is nameof(SingleStoreDbFunctionsExtensions.Match)
+ or nameof(SingleStoreDbFunctionsExtensions.IsMatch)))
.Concat(typeof(string).GetRuntimeMethods().Where(m => m.Name == nameof(string.Concat)))
.Where(m => m.GetParameters().Any(p => p.ParameterType.IsArray))
.ToArray();
+ protected static readonly MethodInfo ElementAtMethodInfo = typeof(Enumerable)
+ .GetRuntimeMethods()
+ .Single(m => m.Name == nameof(Enumerable.ElementAt) &&
+ m.GetParameters()
+ .Select(
+ p => p.ParameterType.IsGenericType
+ ? p.ParameterType.GetGenericTypeDefinition()
+ : p.ParameterType)
+ .SequenceEqual(new[] { typeof(IEnumerable<>), typeof(int) }));
+
public SingleStoreSqlTranslatingExpressionVisitor(
RelationalSqlTranslatingExpressionVisitorDependencies dependencies,
QueryCompilationContext queryCompilationContext,
@@ -40,10 +53,36 @@ public SingleStoreSqlTranslatingExpressionVisitor(
[CanBeNull] ISingleStoreJsonPocoTranslator jsonPocoTranslator)
: base(dependencies, queryCompilationContext, queryableMethodTranslatingExpressionVisitor)
{
+ _queryCompilationContext = queryCompilationContext;
_jsonPocoTranslator = jsonPocoTranslator;
_sqlExpressionFactory = (SingleStoreSqlExpressionFactory)Dependencies.SqlExpressionFactory;
}
+ protected override Expression VisitExtension(Expression extensionExpression)
+ => extensionExpression switch
+ {
+ SingleStoreBipolarExpression bipolarExpression => VisitSingleStoreBipolarExpression(bipolarExpression),
+ _ => base.VisitExtension(extensionExpression)
+ };
+
+ private Expression VisitSingleStoreBipolarExpression(SingleStoreBipolarExpression bipolarExpression)
+ {
+ var defaultExpression = Visit(bipolarExpression.DefaultExpression) ?? QueryCompilationContext.NotTranslatedExpression;
+ var alternativeExpression = Visit(bipolarExpression.AlternativeExpression) ?? QueryCompilationContext.NotTranslatedExpression;
+
+ return defaultExpression != QueryCompilationContext.NotTranslatedExpression
+ // ? alternativeExpression != QueryCompilationContext.NotTranslatedExpression
+ // // ? new SingleStoreBipolarSqlExpression(
+ // // (SqlExpression)defaultExpression,
+ // // (SqlExpression)alternativeExpression)
+ // ? QueryCompilationContext.NotTranslatedExpression
+ // : (SqlExpression)defaultExpression
+ ? (SqlExpression)defaultExpression
+ : alternativeExpression != QueryCompilationContext.NotTranslatedExpression
+ ? (SqlExpression)alternativeExpression
+ : QueryCompilationContext.NotTranslatedExpression;
+ }
+
///
protected override Expression VisitUnary(UnaryExpression unaryExpression)
{
@@ -116,26 +155,7 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
if (binaryExpression.Left.Type == typeof(byte[]))
{
- if (Visit(binaryExpression.Left) is SqlExpression leftSql &&
- Visit(binaryExpression.Right) is SqlExpression rightSql)
- {
- return _sqlExpressionFactory.NullableFunction(
- "ASCII",
- new[]
- {
- _sqlExpressionFactory.NullableFunction(
- "SUBSTRING",
- new[]
- {
- leftSql, Dependencies.SqlExpressionFactory.Add(
- Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql),
- Dependencies.SqlExpressionFactory.Constant(1)),
- Dependencies.SqlExpressionFactory.Constant(1)
- },
- typeof(byte[]))
- },
- typeof(byte));
- }
+ return TranslateByteArrayElementAccess(sqlLeft, sqlRight);
}
// Try translating ArrayIndex inside json column
@@ -284,6 +304,27 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
return base.VisitBinary(binaryExpression);
}
+ private Expression TranslateByteArrayElementAccess(Expression array, Expression index)
+ => Visit(array) is SqlExpression leftSql &&
+ Visit(index) is SqlExpression rightSql
+ ? _sqlExpressionFactory.NullableFunction(
+ "ASCII",
+ new[]
+ {
+ _sqlExpressionFactory.NullableFunction(
+ "SUBSTRING",
+ new[]
+ {
+ leftSql, Dependencies.SqlExpressionFactory.Add(
+ Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql),
+ Dependencies.SqlExpressionFactory.Constant(1)),
+ Dependencies.SqlExpressionFactory.Constant(1)
+ },
+ typeof(byte[]))
+ },
+ typeof(byte))
+ : QueryCompilationContext.NotTranslatedExpression;
+
protected virtual Expression VisitMethodCallNewArray(NewArrayExpression newArrayExpression)
{
// Needed for SingleStoreDbFunctionsExtensions.Match() and String.Concat() translation.
@@ -313,6 +354,15 @@ protected virtual Expression VisitMethodCallNewArray(NewArrayExpression newArray
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
+ if (methodCallExpression.Method.IsGenericMethod
+ && methodCallExpression.Method.GetGenericMethodDefinition() == ElementAtMethodInfo
+ && methodCallExpression.Arguments[0].Type == typeof(byte[]))
+ {
+ return TranslateByteArrayElementAccess(
+ methodCallExpression.Arguments[0],
+ methodCallExpression.Arguments[1]);
+ }
+
if (NewArrayExpressionSupportMethodInfos.Contains(methodCallExpression.Method))
{
var arguments = new Expression[methodCallExpression.Arguments.Count];
@@ -338,7 +388,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
methodCallExpression = methodCallExpression.Update(methodCallExpression.Object, arguments);
}
- var result = base.VisitMethodCall(methodCallExpression);
+ var result = CallBaseVisitMethodCall(methodCallExpression);
+
if (result == QueryCompilationContext.NotTranslatedExpression &&
SingleStoreStringComparisonMethodTranslator.StringComparisonMethodInfos.Any(m => m == methodCallExpression.Method))
{
@@ -364,6 +415,36 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
return result;
}
+ ///
+ /// EF Core does not forward the current QueryCompilationContext to IMethodCallTranslator implementations.
+ /// Our SingleStoreMethodCallTranslatorProvider and SingleStoreQueryCompilationContextMethodTranslator implementations take care of that.
+ ///
+ private Expression CallBaseVisitMethodCall(MethodCallExpression methodCallExpression)
+ {
+ var mySqlMethodCallTranslatorProvider = (SingleStoreMethodCallTranslatorProvider)Dependencies.MethodCallTranslatorProvider;
+
+ if (mySqlMethodCallTranslatorProvider.QueryCompilationContext is null)
+ {
+ mySqlMethodCallTranslatorProvider.QueryCompilationContext = _queryCompilationContext;
+
+ try
+ {
+ return base.VisitMethodCall(methodCallExpression);
+ }
+ finally
+ {
+ mySqlMethodCallTranslatorProvider.QueryCompilationContext = null;
+ }
+ }
+
+ if (mySqlMethodCallTranslatorProvider.QueryCompilationContext == _queryCompilationContext)
+ {
+ return base.VisitMethodCall(methodCallExpression);
+ }
+
+ throw new UnreachableException();
+ }
+
protected virtual void ResetTranslationErrorDetails()
{
// When we try translating an expression and later decide that we want to discard the result, we need to remove any translation
diff --git a/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreBipolarExpression.cs b/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreBipolarExpression.cs
new file mode 100644
index 000000000..9fc598f23
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreBipolarExpression.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace EntityFrameworkCore.SingleStore.Query.Expressions.Internal;
+
+public class SingleStoreBipolarExpression : Expression
+{
+ public SingleStoreBipolarExpression(
+ Expression defaultExpression,
+ Expression alternativeExpression)
+ {
+ Check.NotNull(defaultExpression, nameof(defaultExpression));
+ Check.NotNull(alternativeExpression, nameof(alternativeExpression));
+
+ DefaultExpression = defaultExpression;
+ AlternativeExpression = alternativeExpression;
+ }
+
+ public virtual Expression DefaultExpression { get; }
+ public virtual Expression AlternativeExpression { get; }
+
+ public override ExpressionType NodeType
+ => ExpressionType.UnaryPlus;
+
+ public override Type Type
+ => DefaultExpression.Type;
+
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
+ var defaultExpression = visitor.Visit(DefaultExpression);
+ var alternativeExpression = visitor.Visit(AlternativeExpression);
+
+ return Update(defaultExpression, alternativeExpression);
+ }
+
+ public virtual SingleStoreBipolarExpression Update(Expression defaultExpression, Expression alternativeExpression)
+ => defaultExpression != DefaultExpression || alternativeExpression != AlternativeExpression
+ ? new SingleStoreBipolarExpression(defaultExpression, alternativeExpression)
+ : this;
+
+ public override string ToString()
+ {
+ var expressionPrinter = new ExpressionPrinter();
+
+ expressionPrinter.AppendLine("(");
+
+ using (expressionPrinter.Indent())
+ {
+ expressionPrinter.Append("Default: ");
+ expressionPrinter.Visit(DefaultExpression);
+ expressionPrinter.AppendLine();
+
+ expressionPrinter.Append("Alternative: ");
+ expressionPrinter.Visit(AlternativeExpression);
+ expressionPrinter.AppendLine();
+ }
+
+ expressionPrinter.Append(")");
+
+ return expressionPrinter.ToString();
+ }
+
+ public override bool Equals(object obj)
+ => obj != null
+ && (ReferenceEquals(this, obj)
+ || obj is SingleStoreBipolarExpression bipolarExpression
+ && Equals(bipolarExpression));
+
+ private bool Equals(SingleStoreBipolarExpression bipolarExpression)
+ => base.Equals(bipolarExpression)
+ && DefaultExpression.Equals(bipolarExpression.DefaultExpression)
+ && AlternativeExpression.Equals(bipolarExpression.AlternativeExpression);
+
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), DefaultExpression, AlternativeExpression);
+}
diff --git a/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreInlinedParameterExpression.cs b/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreInlinedParameterExpression.cs
new file mode 100644
index 000000000..bf4291058
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreInlinedParameterExpression.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace EntityFrameworkCore.SingleStore.Query.Expressions.Internal;
+
+public class SingleStoreInlinedParameterExpression : SqlExpression
+{
+ public SingleStoreInlinedParameterExpression(
+ SqlParameterExpression parameterExpression,
+ SqlConstantExpression valueExpression)
+ : base(parameterExpression.Type, parameterExpression.TypeMapping)
+ {
+ Check.NotNull(parameterExpression, nameof(parameterExpression));
+
+ ParameterExpression = parameterExpression;
+ ValueExpression = valueExpression;
+ }
+
+ public virtual Expression ParameterExpression { get; }
+ public virtual SqlConstantExpression ValueExpression { get; }
+
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
+ var parameterExpression = (SqlParameterExpression)visitor.Visit(ParameterExpression);
+ var valueExpression = (SqlConstantExpression)visitor.Visit(ValueExpression);
+
+ return Update(parameterExpression, valueExpression);
+ }
+
+ protected override void Print(ExpressionPrinter expressionPrinter)
+ {
+ expressionPrinter.Visit(ValueExpression);
+ expressionPrinter.Append(" (<<== ");
+ expressionPrinter.Visit(ParameterExpression);
+ expressionPrinter.Append(")");
+ }
+
+ public virtual SingleStoreInlinedParameterExpression Update(SqlParameterExpression parameterExpression, SqlConstantExpression valueExpression)
+ => parameterExpression != ParameterExpression || valueExpression != ValueExpression
+ ? new SingleStoreInlinedParameterExpression(parameterExpression, valueExpression)
+ : this;
+
+ public override bool Equals(object obj)
+ => obj != null
+ && (ReferenceEquals(this, obj)
+ || obj is SingleStoreInlinedParameterExpression inlinedParameterExpression
+ && Equals(inlinedParameterExpression));
+
+ private bool Equals(SingleStoreInlinedParameterExpression inlinedParameterExpression)
+ => base.Equals(inlinedParameterExpression)
+ && ParameterExpression.Equals(inlinedParameterExpression.ParameterExpression)
+ && ValueExpression.Equals(inlinedParameterExpression.ValueExpression);
+
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), ParameterExpression, ValueExpression);
+}
diff --git a/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreMatchExpression.cs b/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreMatchExpression.cs
index 3c628871f..cd49b836b 100644
--- a/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreMatchExpression.cs
+++ b/src/EFCore.SingleStore/Query/Expressions/Internal/SingleStoreMatchExpression.cs
@@ -19,7 +19,7 @@ public SingleStoreMatchExpression(
SqlExpression match,
SqlExpression against,
RelationalTypeMapping typeMapping)
- : base(typeof(bool), typeMapping)
+ : base(typeof(double), typeMapping)
{
Check.NotNull(match, nameof(match));
Check.NotNull(against, nameof(against));
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMethodTranslator.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMethodTranslator.cs
index b6e3906d9..2f8cf0de7 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMethodTranslator.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMethodTranslator.cs
@@ -23,19 +23,25 @@ public class SingleStoreDateTimeMethodTranslator : IMethodCallTranslator
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddHours), new[] { typeof(double) }), "hour" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMinutes), new[] { typeof(double) }), "minute" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddSeconds), new[] { typeof(double) }), "second" },
- { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMilliseconds), new[] { typeof(double) }), "microsecond" },
+ { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMilliseconds), new[] { typeof(double) }), "millisecond" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddYears), new[] { typeof(int) }), "year" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMonths), new[] { typeof(int) }), "month" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddDays), new[] { typeof(double) }), "day" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddHours), new[] { typeof(double) }), "hour" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMinutes), new[] { typeof(double) }), "minute" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddSeconds), new[] { typeof(double) }), "second" },
- { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMilliseconds), new[] { typeof(double) }), "microsecond" },
+ { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMilliseconds), new[] { typeof(double) }), "millisecond" },
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), new[] { typeof(int) }), "year" },
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddMonths), new[] { typeof(int) }), "month" },
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddDays), new[] { typeof(int) }), "day" },
};
+ private static readonly Dictionary _methodInfoDateDiffMapping = new()
+ {
+ { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.ToUnixTimeSeconds), Type.EmptyTypes)!, "second" },
+ { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.ToUnixTimeMilliseconds), Type.EmptyTypes)!, "millisecond" }
+ };
+
private static readonly MethodInfo _timeOnlyAddTimeSpanMethod = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.Add), new[] { typeof(TimeSpan) })!;
private static readonly MethodInfo _timeOnlyAddHoursMethod = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.AddHours), new[] {typeof(double)})!;
private static readonly MethodInfo _timeOnlyAddMinutesMethod = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.AddMinutes), new[] {typeof(double)})!;
@@ -72,12 +78,15 @@ public virtual SqlExpression Translate(
new SqlExpression[]
{
_sqlExpressionFactory.Fragment("INTERVAL"),
- datePart.Equals("microsecond")
+ datePart.Equals("millisecond")
? _sqlExpressionFactory.Multiply(
_sqlExpressionFactory.Constant(1000),
_sqlExpressionFactory.Convert(arguments[0], typeof(int)))
: _sqlExpressionFactory.Convert(arguments[0], typeof(int)),
- _sqlExpressionFactory.Fragment(datePart)
+ _sqlExpressionFactory.Fragment(
+ datePart == "millisecond"
+ ? "microsecond"
+ : datePart)
},
" ",
typeof(string))
@@ -88,6 +97,35 @@ public virtual SqlExpression Translate(
new[] {true, false});
}
+ if (method.DeclaringType == typeof(DateTimeOffset) &&
+ instance is not null)
+ {
+ if (_methodInfoDateDiffMapping.TryGetValue(method, out var timePart))
+ {
+ SqlExpression expression = _sqlExpressionFactory.NullableFunction(
+ "TIMESTAMPDIFF",
+ new[]
+ {
+ _sqlExpressionFactory.Fragment(timePart == "millisecond" ? "microsecond" : timePart),
+ _sqlExpressionFactory.Constant(DateTimeOffset.UnixEpoch, instance!.TypeMapping),
+ instance
+ },
+ typeof(long),
+ typeMapping: null,
+ onlyNullWhenAnyNullPropagatingArgumentIsNull: true,
+ argumentsPropagateNullability: new[] { false, true, true });
+
+ if (timePart == "millisecond")
+ {
+ expression = _sqlExpressionFactory.SingleStoreIntegerDivide(
+ expression,
+ _sqlExpressionFactory.Constant(1_000));
+ }
+
+ return expression;
+ }
+ }
+
if (method.DeclaringType == typeof(TimeOnly))
{
if (method == _timeOnlyAddTimeSpanMethod)
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreMathMethodTranslator.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreMathMethodTranslator.cs
index b85a9b4d6..70aadf0c4 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreMathMethodTranslator.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreMathMethodTranslator.cs
@@ -43,6 +43,11 @@ public class SingleStoreMathMethodTranslator : IMethodCallTranslator
{ typeof(Math).GetRuntimeMethod(nameof(Math.Cos), new[] { typeof(double) }), ("COS", true) },
{ typeof(MathF).GetRuntimeMethod(nameof(MathF.Cos), new[] { typeof(float) }), ("COS", true) },
+ { typeof(double).GetRuntimeMethod(nameof(double.DegreesToRadians), new[] { typeof(double) })!, ("RADIANS", true) },
+ { typeof(float).GetRuntimeMethod(nameof(float.DegreesToRadians), new[] { typeof(float) })!, ("RADIANS", true) },
+ { typeof(double).GetRuntimeMethod(nameof(double.RadiansToDegrees), new[] { typeof(double) })!, ("DEGREES", true) },
+ { typeof(float).GetRuntimeMethod(nameof(float.RadiansToDegrees), new[] { typeof(float) })!, ("DEGREES", true) },
+
{ typeof(Math).GetRuntimeMethod(nameof(Math.Exp), new[] { typeof(double) }), ("EXP", true) },
{ typeof(MathF).GetRuntimeMethod(nameof(MathF.Exp), new[] { typeof(float) }), ("EXP", true) },
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs
index 8504d7a0d..477800d3b 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs
@@ -2,8 +2,15 @@
// Copyright (c) SingleStore Inc. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System;
+using System.Collections.Generic;
+using System.Reflection;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal;
using EntityFrameworkCore.SingleStore.Storage.Internal;
@@ -32,9 +39,22 @@ public SingleStoreMethodCallTranslatorProvider(
new SingleStoreNewGuidTranslator(sqlExpressionFactory),
new SingleStoreObjectToStringTranslator(sqlExpressionFactory),
new SingleStoreRegexIsMatchTranslator(sqlExpressionFactory),
- new SingleStoreStringComparisonMethodTranslator(sqlExpressionFactory, options),
- new SingleStoreStringMethodTranslator(sqlExpressionFactory, relationalTypeMappingSource, options),
+ new SingleStoreStringComparisonMethodTranslator(sqlExpressionFactory, () => QueryCompilationContext, options),
+ new SingleStoreStringMethodTranslator(sqlExpressionFactory, relationalTypeMappingSource, () => QueryCompilationContext, options),
});
}
+
+
+ public virtual QueryCompilationContext QueryCompilationContext { get; set; }
+
+ public override SqlExpression Translate(
+ IModel model,
+ SqlExpression instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ IDiagnosticsLogger logger)
+ => QueryCompilationContext is not null
+ ? base.Translate(model, instance, method, arguments, logger)
+ : throw new InvalidOperationException();
}
}
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreParameterBasedSqlProcessor.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreParameterBasedSqlProcessor.cs
index d138f5b81..e5373bc35 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreParameterBasedSqlProcessor.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreParameterBasedSqlProcessor.cs
@@ -17,7 +17,6 @@ namespace EntityFrameworkCore.SingleStore.Query.Internal
public class SingleStoreParameterBasedSqlProcessor : RelationalParameterBasedSqlProcessor
{
private readonly ISingleStoreOptions _options;
- private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory;
public SingleStoreParameterBasedSqlProcessor(
RelationalParameterBasedSqlProcessorDependencies dependencies,
@@ -25,7 +24,6 @@ public SingleStoreParameterBasedSqlProcessor(
ISingleStoreOptions options)
: base(dependencies, useRelationalNulls)
{
- _sqlExpressionFactory = (SingleStoreSqlExpressionFactory)Dependencies.SqlExpressionFactory;
_options = options;
}
@@ -49,7 +47,14 @@ public override Expression Optimize(
queryExpression = new SingleStoreBoolOptimizingExpressionVisitor(Dependencies.SqlExpressionFactory).Visit(queryExpression);
}
- queryExpression = new SingleStoreHavingExpressionVisitor(_sqlExpressionFactory).Visit(queryExpression);
+ queryExpression = new SingleStoreHavingExpressionVisitor((SingleStoreSqlExpressionFactory)Dependencies.SqlExpressionFactory).Visit(queryExpression);
+
+ queryExpression = new SingleStoreParameterInliningExpressionVisitor(
+ Dependencies.TypeMappingSource,
+ Dependencies.SqlExpressionFactory,
+ _options).Process(queryExpression, parametersValues, out var canCache3);
+
+ canCache &= canCache3;
// Run the compatibility checks as late in the query pipeline (before the actual SQL translation happens) as reasonable.
queryExpression = new SingleStoreCompatibilityExpressionVisitor(_options).Visit(queryExpression);
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryCompilationContextMethodTranslator.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryCompilationContextMethodTranslator.cs
new file mode 100644
index 000000000..4cdef492f
--- /dev/null
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryCompilationContextMethodTranslator.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+namespace EntityFrameworkCore.SingleStore.Query.Internal;
+
+public abstract class SingleStoreQueryCompilationContextMethodTranslator : IMethodCallTranslator
+{
+ private readonly Func _queryCompilationContextResolver;
+
+ protected SingleStoreQueryCompilationContextMethodTranslator(Func queryCompilationContextResolver)
+ {
+ _queryCompilationContextResolver = queryCompilationContextResolver;
+ }
+
+ public virtual SqlExpression Translate(
+ SqlExpression instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ IDiagnosticsLogger logger)
+ => Translate(instance, method, arguments, _queryCompilationContextResolver() ?? throw new InvalidOperationException());
+
+ public abstract SqlExpression Translate(
+ SqlExpression instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ QueryCompilationContext queryCompilationContext);
+}
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitor.cs
index df0e96635..e9d829087 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitor.cs
@@ -2,30 +2,83 @@
// Copyright (c) SingleStore Inc. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Utilities;
+using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
+using EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal;
+using EntityFrameworkCore.SingleStore.Storage.Internal;
namespace EntityFrameworkCore.SingleStore.Query.Internal;
public class SingleStoreQueryableMethodTranslatingExpressionVisitor : RelationalQueryableMethodTranslatingExpressionVisitor
{
+ private readonly ISingleStoreOptions _options;
+ private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory;
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
+
public SingleStoreQueryableMethodTranslatingExpressionVisitor(
QueryableMethodTranslatingExpressionVisitorDependencies dependencies,
RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies,
- QueryCompilationContext queryCompilationContext)
+ QueryCompilationContext queryCompilationContext,
+ ISingleStoreOptions options)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
+ _sqlExpressionFactory = (SingleStoreSqlExpressionFactory)relationalDependencies.SqlExpressionFactory;
+ _typeMappingSource = relationalDependencies.TypeMappingSource;
+ _options = options;
+ }
+
+ protected SingleStoreQueryableMethodTranslatingExpressionVisitor(
+ SingleStoreQueryableMethodTranslatingExpressionVisitor parentVisitor)
+ : base(parentVisitor)
+ {
+ _sqlExpressionFactory = parentVisitor._sqlExpressionFactory;
+ _typeMappingSource = parentVisitor._typeMappingSource;
+ _options = parentVisitor._options;
+ }
+
+ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor()
+ => new SingleStoreQueryableMethodTranslatingExpressionVisitor(this);
+
+ protected override bool IsNaturallyOrdered(SelectExpression selectExpression)
+ {
+ return selectExpression is
+ {
+ Tables: [var mainTable, ..],
+ Orderings:
+ [
+ {
+ Expression: ColumnExpression { Name: "key", Table: var orderingTable } orderingColumn,
+ IsAscending: true
+ }
+ ]
+ }
+ && orderingTable == mainTable
+ && IsJsonEachKeyColumn(orderingColumn);
+
+ bool IsJsonEachKeyColumn(ColumnExpression orderingColumn)
+ => orderingColumn.Table is SingleStoreJsonTableExpression
+ || (orderingColumn.Table is SelectExpression subquery
+ && subquery.Projection.FirstOrDefault(p => p.Alias == "key")?.Expression is ColumnExpression projectedColumn
+ && IsJsonEachKeyColumn(projectedColumn));
}
protected override bool IsValidSelectExpressionForExecuteDelete(
SelectExpression selectExpression,
- EntityShaperExpression entityShaperExpression,
+ StructuralTypeShaperExpression shaper,
[NotNullWhen(true)] out TableExpression tableExpression)
{
if (selectExpression.Offset == null
- && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null)
&& selectExpression.GroupBy.Count == 0
&& selectExpression.Having == null
&& (selectExpression.Tables.Count == 1 || selectExpression.Orderings.Count == 0))
@@ -37,9 +90,9 @@ protected override bool IsValidSelectExpressionForExecuteDelete(
}
else
{
- var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
- var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
- var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First());
+ var projectionBindingExpression = (ProjectionBindingExpression)shaper.ValueBufferExpression;
+ var entityProjectionExpression = (StructuralTypeProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
+ var column = entityProjectionExpression.BindProperty(shaper.StructuralType.GetProperties().First());
table = column.Table;
if (table is JoinExpressionBase joinExpressionBase)
{
@@ -60,15 +113,17 @@ protected override bool IsValidSelectExpressionForExecuteDelete(
protected override bool IsValidSelectExpressionForExecuteUpdate(
SelectExpression selectExpression,
- EntityShaperExpression entityShaperExpression,
+ TableExpressionBase targetTable,
[NotNullWhen(true)] out TableExpression tableExpression)
{
- if (selectExpression.Offset == null
- // If entity type has primary key then Distinct is no-op
- && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null)
- && selectExpression.GroupBy.Count == 0
- && selectExpression.Having == null
- && selectExpression.Orderings.Count == 0)
+ if (selectExpression is
+ {
+ Offset: null,
+ IsDistinct: false,
+ GroupBy: [],
+ Having: null,
+ Orderings: []
+ })
{
TableExpressionBase table;
if (selectExpression.Tables.Count == 1)
@@ -77,11 +132,11 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
}
else
{
- var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
- var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
- var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First());
- table = column.Table;
- if (table is JoinExpressionBase joinExpressionBase)
+ table = targetTable;
+
+ if (selectExpression.Tables.Count > 1 &&
+ table is JoinExpressionBase joinExpressionBase)
+
{
table = joinExpressionBase.Table;
}
@@ -97,4 +152,355 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
tableExpression = null;
return false;
}
+
+ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression source, LambdaExpression predicate)
+ {
+ // Simplify x.Array.Any() => JSON_LENGTH(x.Array) > 0 instead of WHERE EXISTS (SELECT 1 FROM JSON_TABLE(x.Array))
+ if (predicate is null
+ && source.QueryExpression is SelectExpression
+ {
+ Tables: [TableValuedFunctionExpression { Name: "JSON_TABLE", Schema: null, IsBuiltIn: true, Arguments: [var array] }],
+ GroupBy: [],
+ Having: null,
+ IsDistinct: false,
+ Limit: null,
+ Offset: null
+ })
+ {
+ var translation =
+ _sqlExpressionFactory.GreaterThan(
+ _sqlExpressionFactory.NullableFunction(
+ "JSON_LENGTH",
+ new[] { array },
+ typeof(int)),
+ _sqlExpressionFactory.Constant(0));
+
+ return source.UpdateQueryExpression(_sqlExpressionFactory.Select(translation));
+ }
+
+ return base.TranslateAny(source, predicate);
+ }
+
+ protected override ShapedQueryExpression TranslateElementAtOrDefault(
+ ShapedQueryExpression source,
+ Expression index,
+ bool returnDefault)
+ {
+ if (!returnDefault
+ && source.QueryExpression is SelectExpression
+ {
+ Tables:
+ [
+ SingleStoreJsonTableExpression
+ {
+ Name: "JSON_TABLE", Schema: null, IsBuiltIn: true, JsonExpression: var jsonArrayColumn
+ } jsonEachExpression
+ ],
+ GroupBy: [],
+ Having: null,
+ IsDistinct: false,
+ Orderings: [{ Expression: ColumnExpression { Name: "key" } orderingColumn, IsAscending: true }],
+ Limit: null,
+ Offset: null
+ } selectExpression
+ && orderingColumn.Table == jsonEachExpression
+ && TranslateExpression(index) is { } translatedIndex)
+ {
+ // Index on JSON array
+
+ // Extract the column projected out of the source, and simplify the subquery to a simple JsonScalarExpression
+ var shaperExpression = source.ShaperExpression;
+ if (shaperExpression is UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression
+ && unaryExpression.Operand.Type.IsNullableType()
+ && unaryExpression.Operand.Type.UnwrapNullableType() == unaryExpression.Type)
+ {
+ shaperExpression = unaryExpression.Operand;
+ }
+
+ if (shaperExpression is ProjectionBindingExpression projectionBindingExpression
+ && selectExpression.GetProjection(projectionBindingExpression) is ColumnExpression projectionColumn)
+ {
+ SqlExpression translation = new JsonScalarExpression(
+ jsonArrayColumn,
+ new[] { new PathSegment(translatedIndex) },
+ projectionColumn.Type,
+ projectionColumn.TypeMapping,
+ projectionColumn.IsNullable);
+
+ // If we have a type mapping (i.e. translating over a column rather than a parameter), apply any necessary server-side
+ // conversions.
+ if (projectionColumn.TypeMapping is not null)
+ {
+ translation = ApplyJsonSqlConversion(
+ translation, _sqlExpressionFactory, projectionColumn.TypeMapping, projectionColumn.IsNullable);
+ }
+
+ return source.UpdateQueryExpression(_sqlExpressionFactory.Select(translation));
+ }
+ }
+
+ return base.TranslateElementAtOrDefault(source, index, returnDefault);
+ }
+
+ // TODO: Implement for EF Core 7 JSON support.
+ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpression jsonQueryExpression)
+ {
+ return base.TransformJsonQueryToTable(jsonQueryExpression);
+ }
+
+ protected override ShapedQueryExpression TranslatePrimitiveCollection(SqlExpression sqlExpression, IProperty property, string tableAlias)
+ {
+ if (!_options.PrimitiveCollectionsSupport)
+ {
+ AddTranslationErrorDetails("Primitive collections support has not been enabled.");
+ return null;
+ }
+
+ // if (!_options.ServerVersion.Supports.JsonTableImplementationUsesImplicitLateralJoin &&
+ // sqlExpression is ColumnExpression)
+ // {
+ // // MariaDB will just return wrong results if the column of an outer table is being referenced.
+ // return null;
+ // }
+
+ // Generate the JSON_TABLE() function expression, and wrap it in a SelectExpression.
+
+ // Note that where the elementTypeMapping is known (i.e. collection columns), we immediately generate JSON_TABLE() with a COLUMNS clause
+ // (i.e. with a columnInfo), which determines the type conversion to apply to the JSON elements coming out.
+ // For parameter collections, the element type mapping will only be inferred and applied later (see
+ // SingleStoreInferredTypeMappingApplier below), at which point the we'll apply it to add the COLUMNS clause.
+ var elementTypeMapping = (RelationalTypeMapping)sqlExpression.TypeMapping?.ElementTypeMapping;
+
+ var jsonTableExpression = new SingleStoreJsonTableExpression(
+ tableAlias,
+ sqlExpression,
+ new[] { new PathSegment(_sqlExpressionFactory.Constant("*", RelationalTypeMapping.NullMapping)) },
+ elementTypeMapping is not null
+ ? new[]
+ {
+ new SingleStoreJsonTableExpression.ColumnInfo
+ {
+ Name = "value",
+ TypeMapping = elementTypeMapping,
+ Path = new[] { new PathSegment(_sqlExpressionFactory.Constant(0, _typeMappingSource.FindMapping(typeof(int)))) },
+ }
+ }
+ : null);
+
+ var elementClrType = sqlExpression.Type.GetSequenceType();
+
+ // If this is a collection property, get the element's nullability out of metadata. Otherwise, this is a parameter property, in
+ // which case we only have the CLR type (note that we cannot produce different SQLs based on the nullability of an *element* in
+ // a parameter collection - our caching mechanism only supports varying by the nullability of the parameter itself (i.e. the
+ // collection).
+ // TODO: if property is non-null, GetElementType() should never be null, but we have #31469 for shadow properties
+ var isElementNullable = property?.GetElementType() is null
+ ? elementClrType.IsNullableType()
+ : property.GetElementType()!.IsNullable;
+
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ var selectExpression = new SelectExpression(
+ jsonTableExpression,
+ columnName: "value",
+ columnType: elementClrType,
+ columnTypeMapping: elementTypeMapping,
+ isElementNullable,
+ identifierColumnName: "key",
+ identifierColumnType: typeof(uint),
+ identifierColumnTypeMapping: _typeMappingSource.FindMapping(typeof(uint)));
+#pragma warning restore EF1001 // Internal EF Core API usage.
+
+ // JSON_TABLE() doesn't guarantee the ordering of the elements coming out; when using JSON_TABLE() without COLUMNS, a [key] column is returned
+ // with the JSON array's ordering, which we can ORDER BY; this option doesn't exist with JSON_TABLE() with COLUMNS, unfortunately.
+ // However, JSON_TABLE() with COLUMNS has better performance, and also applies JSON-specific conversions we cannot be done otherwise
+ // (e.g. JSON_TABLE() with COLUMNS does base64 decoding for VARBINARY).
+ // Here we generate JSON_TABLE() with COLUMNS, but also add an ordering by [key] - this is a temporary invalid representation.
+ // In MySqlQueryTranslationPostprocessor, we'll post-process the expression; if the ORDER BY was stripped (e.g. because of
+ // IN, EXISTS or a set operation), we'll just leave the JSON_TABLE() with COLUMNS. If not, we'll convert the JSON_TABLE() with COLUMNS to an
+ // JSON_TABLE() without COLUMNS.
+ // Note that the JSON_TABLE() 'key' column is an nvarchar - we convert it to an int before sorting.
+ selectExpression.AppendOrdering(
+ new OrderingExpression(
+ selectExpression.CreateColumnExpression(
+ jsonTableExpression,
+ "key",
+ typeof(uint),
+ typeMapping: _typeMappingSource.FindMapping(typeof(uint)),
+ columnNullable: false),
+ ascending: true));
+
+ var shaperExpression = (Expression)new ProjectionBindingExpression(selectExpression, new ProjectionMember(), elementClrType.MakeNullable());
+ if (shaperExpression.Type != elementClrType)
+ {
+ Check.DebugAssert(
+ elementClrType.MakeNullable() == shaperExpression.Type,
+ "expression.Type must be nullable of targetType");
+
+ shaperExpression = Expression.Convert(shaperExpression, elementClrType);
+ }
+
+ return new ShapedQueryExpression(selectExpression, shaperExpression);
+ }
+
+ protected override Expression ApplyInferredTypeMappings(
+ Expression expression,
+ IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings)
+ => new SingleStoreInferredTypeMappingApplier(
+ RelationalDependencies.Model, _typeMappingSource, _sqlExpressionFactory, inferredTypeMappings).Visit(expression);
+
+ ///
+ /// Wraps the given expression with any SQL logic necessary to convert a value coming out of a JSON document into the relational value
+ /// represented by the given type mapping.
+ ///
+ private static SqlExpression ApplyJsonSqlConversion(
+ SqlExpression expression,
+ ISqlExpressionFactory sqlExpressionFactory,
+ RelationalTypeMapping typeMapping,
+ bool isNullable)
+ => typeMapping switch
+ {
+ // TODO: EF Core 8 - Decide between UNHEX() and FROM_BASE64().
+ ByteArrayTypeMapping => sqlExpressionFactory.Function("FROM_BASE64", new[] { expression }, isNullable, new[] { true }, typeof(byte[]), typeMapping),
+ _ => expression
+ };
+
+ protected class SingleStoreInferredTypeMappingApplier : RelationalInferredTypeMappingApplier
+ {
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private Dictionary _currentSelectInferredTypeMappings;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SingleStoreInferredTypeMappingApplier(
+ IModel model,
+ IRelationalTypeMappingSource typeMappingSource,
+ ISqlExpressionFactory sqlExpressionFactory,
+ IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping> inferredTypeMappings)
+ : base(model, sqlExpressionFactory, inferredTypeMappings)
+ {
+ (_typeMappingSource, _sqlExpressionFactory) = (typeMappingSource, sqlExpressionFactory);
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override Expression VisitExtension(Expression expression)
+ {
+ switch (expression)
+ {
+ case SingleStoreJsonTableExpression { Name: "JSON_TABLE", Schema: null, IsBuiltIn: true } jsonTableExpression
+ when TryGetInferredTypeMapping(jsonTableExpression, "value", out var typeMapping):
+ return ApplyTypeMappingsOnJsonTableExpression(jsonTableExpression, typeMapping);
+
+ // Above, we applied the type mapping the the parameter that JSON_TABLE accepts as an argument.
+ // But the inferred type mapping also needs to be applied as a SQL conversion on the column projections coming out of the
+ // SelectExpression containing the JSON_TABLE call. So we set state to know about JSON_TABLE tables and their type mappings
+ // in the immediate SelectExpression, and continue visiting down (see ColumnExpression visitation below).
+ case SelectExpression selectExpression:
+ {
+ Dictionary previousSelectInferredTypeMappings = null;
+
+ foreach (var table in selectExpression.Tables)
+ {
+ if (table is TableValuedFunctionExpression { Name: "JSON_TABLE", Schema: null, IsBuiltIn: true } jsonTableExpression
+ && TryGetInferredTypeMapping(jsonTableExpression, "value", out var inferredTypeMapping))
+ {
+ if (previousSelectInferredTypeMappings is null)
+ {
+ previousSelectInferredTypeMappings = _currentSelectInferredTypeMappings;
+ _currentSelectInferredTypeMappings = new Dictionary();
+ }
+
+ _currentSelectInferredTypeMappings![jsonTableExpression] = inferredTypeMapping;
+ }
+ }
+
+ var visited = base.VisitExtension(expression);
+
+ _currentSelectInferredTypeMappings = previousSelectInferredTypeMappings;
+
+ return visited;
+ }
+
+ // Note that we match also ColumnExpressions which already have a type mapping, i.e. coming out of column collections (as
+ // opposed to parameter collections, where the type mapping needs to be inferred). This is in order to apply SQL conversion
+ // logic later in the process, see note in TranslateCollection.
+ case ColumnExpression { Name: "value" } columnExpression
+ when _currentSelectInferredTypeMappings?.TryGetValue(columnExpression.Table, out var inferredTypeMapping) is true:
+ return ApplyJsonSqlConversion(
+ columnExpression.ApplyTypeMapping(inferredTypeMapping),
+ _sqlExpressionFactory,
+ inferredTypeMapping,
+ columnExpression.IsNullable);
+
+ default:
+ return base.VisitExtension(expression);
+ }
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual TableValuedFunctionExpression ApplyTypeMappingsOnJsonTableExpression(
+ SingleStoreJsonTableExpression jsonTableExpression,
+ RelationalTypeMapping inferredTypeMapping)
+ {
+ // Constant queryables are translated to VALUES, no need for JSON.
+ // Column queryables have their type mapping from the model, so we don't ever need to apply an inferred mapping on them.
+ if (jsonTableExpression.JsonExpression is not SqlParameterExpression parameterExpression)
+ {
+ return jsonTableExpression;
+ }
+
+ if (_typeMappingSource.FindMapping(parameterExpression.Type, Model, inferredTypeMapping) is not SingleStoreStringTypeMapping
+ parameterTypeMapping)
+ {
+ throw new InvalidOperationException("Type mapping for 'string' could not be found or was not a SingleStoreStringTypeMapping");
+ }
+
+ Check.DebugAssert(parameterTypeMapping.ElementTypeMapping != null, "Collection type mapping missing element mapping.");
+
+ return jsonTableExpression.Update(
+ parameterExpression.ApplyTypeMapping(parameterTypeMapping),
+ jsonTableExpression.Path,
+ new[]
+ {
+ new SingleStoreJsonTableExpression.ColumnInfo
+ {
+ Name = "value",
+ TypeMapping = (RelationalTypeMapping)parameterTypeMapping.ElementTypeMapping,
+ Path = new[] { new PathSegment(_sqlExpressionFactory.Constant(0, _typeMappingSource.FindMapping(typeof(int)))) },
+ }
+ });
+ }
+ }
+
+ private sealed class FakeMemberInfo : MemberInfo
+ {
+ public FakeMemberInfo(string name)
+ => Name = name;
+
+ public override string Name { get; }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ => throw new NotSupportedException();
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ => throw new NotSupportedException();
+ public override bool IsDefined(Type attributeType, bool inherit)
+ => throw new NotSupportedException();
+ public override Type DeclaringType => throw new NotSupportedException();
+ public override MemberTypes MemberType => throw new NotSupportedException();
+ public override Type ReflectedType => throw new NotSupportedException();
+ }
}
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitorFactory.cs
index 2cbf980fc..2fdab9391 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitorFactory.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreQueryableMethodTranslatingExpressionVisitorFactory.cs
@@ -3,11 +3,14 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using Microsoft.EntityFrameworkCore.Query;
+using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
namespace EntityFrameworkCore.SingleStore.Query.Internal;
public class SingleStoreQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory
{
+ private readonly ISingleStoreOptions _options;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -16,10 +19,12 @@ public class SingleStoreQueryableMethodTranslatingExpressionVisitorFactory : IQu
///
public SingleStoreQueryableMethodTranslatingExpressionVisitorFactory(
QueryableMethodTranslatingExpressionVisitorDependencies dependencies,
- RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies)
+ RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies,
+ ISingleStoreOptions options)
{
Dependencies = dependencies;
RelationalDependencies = relationalDependencies;
+ _options = options;
}
///
@@ -39,5 +44,5 @@ public SingleStoreQueryableMethodTranslatingExpressionVisitorFactory(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext)
- => new SingleStoreQueryableMethodTranslatingExpressionVisitor(Dependencies, RelationalDependencies, queryCompilationContext);
+ => new SingleStoreQueryableMethodTranslatingExpressionVisitor(Dependencies, RelationalDependencies, queryCompilationContext, _options);
}
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreSqlExpressionFactory.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreSqlExpressionFactory.cs
index b19d9138d..c66f8c6ad 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreSqlExpressionFactory.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreSqlExpressionFactory.cs
@@ -21,12 +21,14 @@ public class SingleStoreSqlExpressionFactory : SqlExpressionFactory
{
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly RelationalTypeMapping _boolTypeMapping;
+ private readonly RelationalTypeMapping _doubleTypeMapping;
public SingleStoreSqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
: base(dependencies)
{
_typeMappingSource = dependencies.TypeMappingSource;
_boolTypeMapping = _typeMappingSource.FindMapping(typeof(bool));
+ _doubleTypeMapping = _typeMappingSource.FindMapping(typeof(double));
}
public virtual RelationalTypeMapping FindMapping(
@@ -383,7 +385,7 @@ private SqlExpression ApplyTypeMappingOnMatch(SingleStoreMatchExpression matchEx
return new SingleStoreMatchExpression(
ApplyTypeMapping(matchExpression.Match, inferredTypeMapping),
ApplyTypeMapping(matchExpression.Against, inferredTypeMapping),
- _boolTypeMapping);
+ _doubleTypeMapping);
}
private SqlExpression ApplyTypeMappingOnRegexp(SingleStoreRegexpExpression regexpExpression)
diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreStringMethodTranslator.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreStringMethodTranslator.cs
index 45109f428..92bcbc606 100644
--- a/src/EFCore.SingleStore/Query/Internal/SingleStoreStringMethodTranslator.cs
+++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreStringMethodTranslator.cs
@@ -6,8 +6,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
@@ -18,9 +16,10 @@
namespace EntityFrameworkCore.SingleStore.Query.Internal
{
- public class SingleStoreStringMethodTranslator : IMethodCallTranslator
+ public class SingleStoreStringMethodTranslator : SingleStoreQueryCompilationContextMethodTranslator
{
private readonly IRelationalTypeMappingSource _typeMappingSource;
+ private readonly Func _queryCompilationContextResolver;
private readonly ISingleStoreOptions _options;
private static readonly MethodInfo _indexOfMethodInfo
@@ -117,28 +116,31 @@ private static readonly MethodInfo _removeMethodInfoWithTwoArgs
public SingleStoreStringMethodTranslator(
SingleStoreSqlExpressionFactory sqlExpressionFactory,
SingleStoreTypeMappingSource typeMappingSource,
+ Func queryCompilationContextResolver,
ISingleStoreOptions options)
+ : base(queryCompilationContextResolver)
{
_sqlExpressionFactory = sqlExpressionFactory;
_typeMappingSource = typeMappingSource;
+ _queryCompilationContextResolver = queryCompilationContextResolver;
_options = options;
}
- public virtual SqlExpression Translate(
+ public override SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList arguments,
- IDiagnosticsLogger logger)
+ QueryCompilationContext queryCompilationContext)
{
if (_indexOfMethodInfo.Equals(method))
{
- return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _options)
+ return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _queryCompilationContextResolver, _options)
.MakeIndexOfExpression(instance, arguments[0]);
}
if(_indexOfMethodInfoWithOneArg.Equals(method))
{
- return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _options)
+ return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _queryCompilationContextResolver, _options)
.MakeIndexOfExpression(instance, arguments[0], startIndex: arguments[1]);
}
@@ -250,20 +252,20 @@ public virtual SqlExpression Translate(
if (_containsMethodInfo.Equals(method))
{
- return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _options)
- .MakeContainsExpression(instance, arguments[0]);
+ return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _queryCompilationContextResolver, _options)
+ .MakeContainsExpression(queryCompilationContext, instance, arguments[0]);
}
if (_startsWithMethodInfo.Equals(method))
{
- return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _options)
- .MakeStartsWithExpression(instance, arguments[0]);
+ return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _queryCompilationContextResolver, _options)
+ .MakeStartsWithExpression(queryCompilationContext, instance, arguments[0]);
}
if (_endsWithMethodInfo.Equals(method))
{
- return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _options)
- .MakeEndsWithExpression(instance, arguments[0]);
+ return new SingleStoreStringComparisonMethodTranslator(_sqlExpressionFactory, _queryCompilationContextResolver, _options)
+ .MakeEndsWithExpression(queryCompilationContext, instance, arguments[0]);
}
if (_padLeftWithOneArg.Equals(method))
diff --git a/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationMemberAccessTypeMapping.cs b/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationMemberAccessTypeMapping.cs
index 54f892757..4e7a2879b 100644
--- a/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationMemberAccessTypeMapping.cs
+++ b/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationMemberAccessTypeMapping.cs
@@ -12,6 +12,8 @@ internal class SingleStoreCodeGenerationMemberAccessTypeMapping : RelationalType
{
private const string DummyStoreType = "clrOnly";
+ public static SingleStoreCodeGenerationMemberAccessTypeMapping Default { get; } = new();
+
public SingleStoreCodeGenerationMemberAccessTypeMapping()
: base(new RelationalTypeMappingParameters(new CoreTypeMappingParameters(typeof(SingleStoreCodeGenerationMemberAccess)), DummyStoreType))
{
diff --git a/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationServerVersionCreationTypeMapping.cs b/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationServerVersionCreationTypeMapping.cs
index c758da93b..8931f6b76 100644
--- a/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationServerVersionCreationTypeMapping.cs
+++ b/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreCodeGenerationServerVersionCreationTypeMapping.cs
@@ -14,6 +14,8 @@ internal class SingleStoreCodeGenerationServerVersionCreationTypeMapping : Relat
{
private const string DummyStoreType = "clrOnly";
+ public static SingleStoreCodeGenerationServerVersionCreationTypeMapping Default { get; } = new();
+
public SingleStoreCodeGenerationServerVersionCreationTypeMapping()
: base(new RelationalTypeMappingParameters(new CoreTypeMappingParameters(typeof(SingleStoreCodeGenerationServerVersionCreation)), DummyStoreType))
{
diff --git a/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreDatabaseModelFactory.cs b/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreDatabaseModelFactory.cs
index 00612373f..3ca095199 100644
--- a/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreDatabaseModelFactory.cs
+++ b/src/EFCore.SingleStore/Scaffolding/Internal/SingleStoreDatabaseModelFactory.cs
@@ -538,7 +538,7 @@ private bool IsDefaultValueSqlFunction(string defaultValue, string dataType)
///
/// MariaDB 10.2.7+ implements default values differently from MySQL, to support their own default expression
/// syntax. We convert their column values to MySQL compatible syntax here.
- /// See https://github.com/PomeloFoundation/EntityFrameworkCore.SingleStore/issues/994#issuecomment-568271740
+ /// See https://github.com/PomeloFoundation/EntityFrameworkCore.MySql/issues/994#issuecomment-568271740
/// for tables with differences.
///
protected virtual string ConvertDefaultValueFromMariaDbToSingleStore([NotNull] string defaultValue, out bool isDefaultValueExpression)
diff --git a/src/EFCore.SingleStore/Storage/Internal/ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.cs b/src/EFCore.SingleStore/Storage/Internal/ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.cs
new file mode 100644
index 000000000..18d9a0343
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.Design.Internal;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public interface ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator
+{
+ void Create(
+ CSharpRuntimeAnnotationCodeGeneratorParameters codeGeneratorParameters,
+ CSharpRuntimeAnnotationCodeGeneratorDependencies codeGeneratorDependencies);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/ISingleStoreConnectionStringOptionsValidator.cs b/src/EFCore.SingleStore/Storage/Internal/ISingleStoreConnectionStringOptionsValidator.cs
new file mode 100644
index 000000000..2832e0add
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/ISingleStoreConnectionStringOptionsValidator.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Data.Common;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public interface ISingleStoreConnectionStringOptionsValidator
+{
+ bool EnsureMandatoryOptions(ref string connectionString);
+ bool EnsureMandatoryOptions(DbConnection connection);
+ bool EnsureMandatoryOptions(DbDataSource dataSource);
+
+ void ThrowException(Exception innerException = null);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/Json/SingleStoreJsonByteArrayAsHexStringReaderWriter.cs b/src/EFCore.SingleStore/Storage/Internal/Json/SingleStoreJsonByteArrayAsHexStringReaderWriter.cs
new file mode 100644
index 000000000..f8bc50c02
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/Json/SingleStoreJsonByteArrayAsHexStringReaderWriter.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Text.Json;
+using Microsoft.EntityFrameworkCore.Storage.Json;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal.Json;
+
+public sealed class SingleStoreJsonByteArrayAsHexStringReaderWriter : JsonValueReaderWriter
+{
+ public static SingleStoreJsonByteArrayAsHexStringReaderWriter Instance { get; } = new();
+
+ private SingleStoreJsonByteArrayAsHexStringReaderWriter()
+ {
+ }
+
+ public override byte[] FromJsonTyped(ref Utf8JsonReaderManager manager, object existingObject = null)
+ => Convert.FromHexString(manager.CurrentReader.GetString()!);
+
+ public override void ToJsonTyped(Utf8JsonWriter writer, byte[] value)
+ => writer.WriteStringValue(Convert.ToHexString(value));
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreBoolTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreBoolTypeMapping.cs
index 98397df00..f61273b1f 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreBoolTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreBoolTypeMapping.cs
@@ -5,6 +5,7 @@
using System.Data;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -16,16 +17,21 @@ namespace EntityFrameworkCore.SingleStore.Storage.Internal
///
public class SingleStoreBoolTypeMapping : BoolTypeMapping
{
+ public static new SingleStoreBoolTypeMapping Default { get; } = new("tinyint", size: 1);
+
public SingleStoreBoolTypeMapping(
[NotNull] string storeType,
- DbType? dbType = null,
+ DbType? dbType = System.Data.DbType.Boolean,
int? size = null)
- : this(new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(bool)),
- storeType,
- size == null ? StoreTypePostfix.None : StoreTypePostfix.Size,
- dbType,
- size: size))
+ : this(
+ new RelationalTypeMappingParameters(
+ new CoreTypeMappingParameters(
+ typeof(bool),
+ jsonValueReaderWriter: JsonBoolReaderWriter.Instance),
+ storeType,
+ size == null ? StoreTypePostfix.None : StoreTypePostfix.Size,
+ dbType,
+ size: size))
{
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteArrayTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteArrayTypeMapping.cs
index 9c350744c..273fca305 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteArrayTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteArrayTypeMapping.cs
@@ -6,7 +6,8 @@
using System.Data;
using System.Data.Common;
using Microsoft.EntityFrameworkCore.Storage;
-using EntityFrameworkCore.SingleStore.Utilities;
+using Microsoft.EntityFrameworkCore.Storage.Json;
+using EntityFrameworkCore.SingleStore.Storage.Internal.Json;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -20,6 +21,8 @@ public class SingleStoreByteArrayTypeMapping : ByteArrayTypeMapping
private readonly int _maxSpecificSize;
+ public static new SingleStoreByteArrayTypeMapping Default { get; } = new();
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -46,7 +49,9 @@ protected SingleStoreByteArrayTypeMapping(
bool fixedLength)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(byte[])),
+ new CoreTypeMappingParameters(
+ typeof(byte[]),
+ jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance /* SingleStoreJsonByteArrayAsHexStringReaderWriter.Instance */),
storeType ?? GetBaseType(size, fixedLength),
GetStoreTypePostfix(size),
type,
@@ -110,6 +115,9 @@ protected override void ConfigureParameter(DbParameter parameter)
///
/// The generated string.
///
- protected override string GenerateNonNullSqlLiteral(object value) => ByteArrayFormatter.ToHex((byte[])value);
+ protected override string GenerateNonNullSqlLiteral(object value)
+ => value is byte[] { Length: > 0 } byteArray
+ ? "0x" + Convert.ToHexString(byteArray)
+ : "X''";
}
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteTypeMapping.cs
new file mode 100644
index 000000000..0c82eab1b
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreByteTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreByteTypeMapping : ByteTypeMapping
+{
+ public static new SingleStoreByteTypeMapping Default { get; } = new("tinyint unsigned");
+
+ public SingleStoreByteTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.Byte)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreByteTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreByteTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreConnectionStringOptionsValidator.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreConnectionStringOptionsValidator.cs
new file mode 100644
index 000000000..373d5f1c9
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreConnectionStringOptionsValidator.cs
@@ -0,0 +1,113 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Data.Common;
+using System.Linq;
+using System.Reflection;
+using SingleStoreConnector;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreConnectionStringOptionsValidator : ISingleStoreConnectionStringOptionsValidator
+{
+ public virtual bool EnsureMandatoryOptions(ref string connectionString)
+ {
+ if (connectionString is not null)
+ {
+ var csb = new SingleStoreConnectionStringBuilder(connectionString);
+ AddConnectionAttributes(csb);
+
+ if (!ValidateMandatoryOptions(csb))
+ {
+ csb.AllowUserVariables = true;
+ csb.UseAffectedRows = false;
+
+ connectionString = csb.ConnectionString;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public virtual bool EnsureMandatoryOptions(DbConnection connection)
+ {
+ if (connection is not null)
+ {
+ var csb = new SingleStoreConnectionStringBuilder(connection.ConnectionString);
+ AddConnectionAttributes(csb);
+
+ if (!ValidateMandatoryOptions(csb))
+ {
+ try
+ {
+ csb.AllowUserVariables = true;
+ csb.UseAffectedRows = false;
+
+ connection.ConnectionString = csb.ConnectionString;
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ ThrowException(e);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public virtual bool EnsureMandatoryOptions(DbDataSource dataSource)
+ {
+ if (dataSource is null)
+ {
+ return false;
+ }
+
+ var csb = new SingleStoreConnectionStringBuilder(dataSource.ConnectionString);
+ AddConnectionAttributes(csb);
+
+ if (!ValidateMandatoryOptions(csb))
+ {
+ // We can't alter the connection string of a DbDataSource/SingleStoreDataSource as we do for DbConnection/SingleStoreConnection in cases
+ // where the necessary connection string options have not been set.
+ // We can only throw.
+ ThrowException();
+ }
+
+ return true;
+ }
+
+ static void AddConnectionAttributes(SingleStoreConnectionStringBuilder csb)
+ {
+ var existing = csb.ConnectionAttributes?.TrimEnd(',') ?? "";
+
+ var programVersion = Assembly.GetExecutingAssembly().GetName().Version;
+ var connAttrs = $"_connector_name:SingleStore Entity Framework Core provider,_connector_version:{programVersion}";
+
+ var existingConnAttrs = existing
+ .Split(',', StringSplitOptions.RemoveEmptyEntries)
+ .Select(attr => attr.Trim())
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
+ if (!existingConnAttrs.Contains(connAttrs))
+ {
+ csb.ConnectionAttributes = string.IsNullOrEmpty(existing)
+ ? connAttrs
+ : $"{existing},{connAttrs}";
+ }
+ }
+
+ public virtual void ThrowException(Exception innerException = null)
+ => throw new InvalidOperationException(
+ @"The connection string of a connection used by EntityFrameworkCore.SingleStore must contain ""AllowUserVariables=True;UseAffectedRows=False"".",
+ innerException);
+
+ protected virtual bool ValidateMandatoryOptions(SingleStoreConnectionStringBuilder csb)
+ => csb.AllowUserVariables &&
+ !csb.UseAffectedRows;
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeOffsetTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeOffsetTypeMapping.cs
index eb43639cd..622d53cde 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeOffsetTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeOffsetTypeMapping.cs
@@ -6,6 +6,7 @@
using System.Data.Common;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -19,6 +20,8 @@ public class SingleStoreDateTimeOffsetTypeMapping : DateTimeOffsetTypeMapping, I
{
private readonly bool _isDefaultValueCompatible;
+ public static new SingleStoreDateTimeOffsetTypeMapping Default { get; } = new("datetime");
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -31,7 +34,9 @@ public SingleStoreDateTimeOffsetTypeMapping(
bool isDefaultValueCompatible = true)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(DateTimeOffset)),
+ new CoreTypeMappingParameters(
+ typeof(DateTimeOffset),
+ jsonValueReaderWriter: JsonDateTimeOffsetReaderWriter.Instance),
storeType,
StoreTypePostfix.Precision,
System.Data.DbType.DateTimeOffset,
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeTypeMapping.cs
index b742d8495..8b79d02fb 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTimeTypeMapping.cs
@@ -6,6 +6,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
@@ -20,6 +21,8 @@ public class SingleStoreDateTimeTypeMapping : DateTimeTypeMapping, IDefaultValue
{
private readonly bool _isDefaultValueCompatible;
+ public static new SingleStoreDateTimeTypeMapping Default { get; } = new("datetime");
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -35,7 +38,11 @@ public SingleStoreDateTimeTypeMapping(
bool isDefaultValueCompatible = true)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(clrType ?? typeof(DateTime), converter, comparer),
+ new CoreTypeMappingParameters(
+ clrType ?? typeof(DateTime),
+ converter,
+ comparer,
+ jsonValueReaderWriter: JsonDateTimeReaderWriter.Instance),
storeType,
StoreTypePostfix.Precision,
System.Data.DbType.DateTime,
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTypeMapping.cs
index 5c56a09c0..41d981a6d 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDateTypeMapping.cs
@@ -5,6 +5,7 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -21,6 +22,8 @@ public class SingleStoreDateTypeMapping : RelationalTypeMapping, IDefaultValueCo
{
private readonly bool _isDefaultValueCompatible;
+ public static SingleStoreDateTypeMapping Default { get; } = new("date", typeof(DateOnly));
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -30,7 +33,9 @@ public class SingleStoreDateTypeMapping : RelationalTypeMapping, IDefaultValueCo
public SingleStoreDateTypeMapping([NotNull] string storeType, Type clrType, bool isDefaultValueCompatible = true)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(clrType),
+ new CoreTypeMappingParameters(
+ clrType,
+ jsonValueReaderWriter: JsonDateOnlyReaderWriter.Instance),
storeType,
dbType: System.Data.DbType.Date),
isDefaultValueCompatible)
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDecimalTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDecimalTypeMapping.cs
index e272ad5ca..6faa63e92 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDecimalTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDecimalTypeMapping.cs
@@ -6,24 +6,29 @@
using System.Data.Common;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Microsoft.EntityFrameworkCore.Storage.Json;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
public class SingleStoreDecimalTypeMapping : DecimalTypeMapping
{
+ public static new SingleStoreDecimalTypeMapping Default { get; } = new("decimal", precision: 65, scale: 30);
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
- public SingleStoreDecimalTypeMapping([NotNull] string storeType,
+ public SingleStoreDecimalTypeMapping(
+ [NotNull] string storeType,
DbType? dbType = null,
int? precision = null,
int? scale = null,
StoreTypePostfix storeTypePostfix = StoreTypePostfix.PrecisionAndScale)
- : this(
+ : base(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(decimal)),
+ new CoreTypeMappingParameters(
+ typeof(decimal),
+ jsonValueReaderWriter: JsonDecimalReaderWriter.Instance),
storeType,
storeTypePostfix,
dbType)
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDoubleTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDoubleTypeMapping.cs
index 97ca71a8d..fa14b22f2 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreDoubleTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreDoubleTypeMapping.cs
@@ -6,7 +6,6 @@
using System.Data.Common;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -18,6 +17,8 @@ namespace EntityFrameworkCore.SingleStore.Storage.Internal
///
public class SingleStoreDoubleTypeMapping : DoubleTypeMapping
{
+ public static new SingleStoreDoubleTypeMapping Default { get; } = new("double");
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -26,7 +27,7 @@ public class SingleStoreDoubleTypeMapping : DoubleTypeMapping
///
public SingleStoreDoubleTypeMapping(
[NotNull] string storeType,
- DbType? dbType = null)
+ DbType? dbType = System.Data.DbType.Double)
: base(storeType, dbType)
{
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreFloatTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreFloatTypeMapping.cs
index 4138cbdd8..454af004d 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreFloatTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreFloatTypeMapping.cs
@@ -7,12 +7,13 @@
using System.Globalization;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
public class SingleStoreFloatTypeMapping : FloatTypeMapping
{
+ public static new SingleStoreFloatTypeMapping Default { get; } = new("float");
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -21,7 +22,7 @@ public class SingleStoreFloatTypeMapping : FloatTypeMapping
///
public SingleStoreFloatTypeMapping(
[NotNull] string storeType,
- DbType? dbType = null)
+ DbType? dbType = System.Data.DbType.Single)
: base(storeType, dbType)
{
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreGuidTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreGuidTypeMapping.cs
index 51e5e3a13..dd66be438 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreGuidTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreGuidTypeMapping.cs
@@ -3,19 +3,26 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
using SingleStoreConnector;
-using EntityFrameworkCore.SingleStore.Utilities;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
- public class SingleStoreGuidTypeMapping : GuidTypeMapping, IJsonSpecificTypeMapping
+ public class SingleStoreGuidTypeMapping : GuidTypeMapping, IJsonSpecificTypeMapping, ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator
{
- private readonly SingleStoreGuidFormat _guidFormat;
+ public static new SingleStoreGuidTypeMapping Default { get; } = new(SingleStoreGuidFormat.Char36);
+
+ public virtual SingleStoreGuidFormat GuidFormat { get; }
public SingleStoreGuidTypeMapping(SingleStoreGuidFormat guidFormat)
: this(new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(Guid)),
+ new CoreTypeMappingParameters(
+ typeof(Guid),
+ jsonValueReaderWriter: JsonGuidReaderWriter.Instance),
GetStoreType(guidFormat),
StoreTypePostfix.Size,
System.Data.DbType.Guid,
@@ -29,18 +36,21 @@ public SingleStoreGuidTypeMapping(SingleStoreGuidFormat guidFormat)
protected SingleStoreGuidTypeMapping(RelationalTypeMappingParameters parameters, SingleStoreGuidFormat guidFormat)
: base(parameters)
{
- _guidFormat = guidFormat;
+ GuidFormat = guidFormat;
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new SingleStoreGuidTypeMapping(parameters, _guidFormat);
+ => new SingleStoreGuidTypeMapping(parameters, GuidFormat);
+
+ public virtual RelationalTypeMapping Clone(SingleStoreGuidFormat guidFormat)
+ => new SingleStoreGuidTypeMapping(Parameters, guidFormat);
public virtual bool IsCharBasedStoreType
- => GetStoreType(_guidFormat) == "char";
+ => GetStoreType(GuidFormat) == "char";
protected override string GenerateNonNullSqlLiteral(object value)
{
- switch (_guidFormat)
+ switch (GuidFormat)
{
case SingleStoreGuidFormat.Char36:
return $"'{value:D}'";
@@ -51,7 +61,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
case SingleStoreGuidFormat.Binary16:
case SingleStoreGuidFormat.TimeSwapBinary16:
case SingleStoreGuidFormat.LittleEndianBinary16:
- return ByteArrayFormatter.ToHex(GetBytesFromGuid(_guidFormat, (Guid)value));
+ return "0x" + Convert.ToHexString(GetBytesFromGuid(GuidFormat, (Guid)value));
case SingleStoreGuidFormat.None:
case SingleStoreGuidFormat.Default:
@@ -128,5 +138,50 @@ protected static byte[] GetBytesFromGuid(SingleStoreGuidFormat guidFormat, Guid
///
public virtual RelationalTypeMapping CloneAsJsonCompatible()
=> new SingleStoreGuidTypeMapping(SingleStoreGuidFormat.Char36);
+
+ void ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.Create(
+ CSharpRuntimeAnnotationCodeGeneratorParameters codeGeneratorParameters,
+ CSharpRuntimeAnnotationCodeGeneratorDependencies codeGeneratorDependencies)
+ {
+ var defaultTypeMapping = Default;
+ if (defaultTypeMapping == this)
+ {
+ return;
+ }
+
+ var code = codeGeneratorDependencies.CSharpHelper;
+
+ var cloneParameters = new List();
+
+ if (GuidFormat != defaultTypeMapping.GuidFormat)
+ {
+ cloneParameters.Add($"guidFormat: {code.Literal(GuidFormat, true)}");
+ }
+
+ if (cloneParameters.Any())
+ {
+ var mainBuilder = codeGeneratorParameters.MainBuilder;
+
+ mainBuilder.AppendLine(";");
+
+ mainBuilder
+ .AppendLine($"{codeGeneratorParameters.TargetName}.TypeMapping = (({code.Reference(GetType())}){codeGeneratorParameters.TargetName}.TypeMapping).Clone(")
+ .IncrementIndent();
+
+ for (var i = 0; i < cloneParameters.Count; i++)
+ {
+ if (i > 0)
+ {
+ mainBuilder.AppendLine(",");
+ }
+
+ mainBuilder.Append(cloneParameters[i]);
+ }
+
+ mainBuilder
+ .Append(")")
+ .DecrementIndent();
+ }
+ }
}
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreIntTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreIntTypeMapping.cs
new file mode 100644
index 000000000..293392bdc
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreIntTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreIntTypeMapping : IntTypeMapping
+{
+ public static new SingleStoreIntTypeMapping Default { get; } = new("int");
+
+ public SingleStoreIntTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.Int32)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreIntTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreIntTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMapping.cs
index d38d7b0e4..914ddf1b6 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMapping.cs
@@ -3,56 +3,68 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Data.Common;
+using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SingleStoreConnector;
-using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
public class SingleStoreJsonTypeMapping : SingleStoreJsonTypeMapping
{
+ public static new SingleStoreJsonTypeMapping Default { get; } = new("json", null, null, false, true);
+
public SingleStoreJsonTypeMapping(
[NotNull] string storeType,
[CanBeNull] ValueConverter valueConverter,
[CanBeNull] ValueComparer valueComparer,
- [NotNull] ISingleStoreOptions options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
: base(
storeType,
typeof(T),
valueConverter,
valueComparer,
- options)
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction)
{
}
protected SingleStoreJsonTypeMapping(
RelationalTypeMappingParameters parameters,
SingleStoreDbType mySqlDbType,
- ISingleStoreOptions options)
- : base(parameters, mySqlDbType, options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
+ : base(parameters, mySqlDbType, noBackslashEscapes, replaceLineBreaksWithCharFunction)
{
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new SingleStoreJsonTypeMapping(parameters, SingleStoreDbType, Options);
+ => new SingleStoreJsonTypeMapping(parameters, SingleStoreDbType, NoBackslashEscapes, ReplaceLineBreaksWithCharFunction);
+
+ protected override RelationalTypeMapping Clone(bool? noBackslashEscapes = null, bool? replaceLineBreaksWithCharFunction = null)
+ => new SingleStoreJsonTypeMapping(
+ Parameters,
+ SingleStoreDbType,
+ noBackslashEscapes ?? NoBackslashEscapes,
+ replaceLineBreaksWithCharFunction ?? ReplaceLineBreaksWithCharFunction);
}
- public abstract class SingleStoreJsonTypeMapping : SingleStoreStringTypeMapping
+ public abstract class SingleStoreJsonTypeMapping : SingleStoreStringTypeMapping, ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator
{
- [NotNull]
- protected virtual ISingleStoreOptions Options { get; }
-
public SingleStoreJsonTypeMapping(
[NotNull] string storeType,
[NotNull] Type clrType,
[CanBeNull] ValueConverter valueConverter,
[CanBeNull] ValueComparer valueComparer,
- [NotNull] ISingleStoreOptions options)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
@@ -62,7 +74,8 @@ public SingleStoreJsonTypeMapping(
storeType,
unicode: true),
SingleStoreDbType.JSON,
- options,
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction,
false,
false)
{
@@ -70,19 +83,30 @@ public SingleStoreJsonTypeMapping(
{
throw new ArgumentException($"The store type '{nameof(storeType)}' must be 'json'.", nameof(storeType));
}
-
- Options = options;
}
protected SingleStoreJsonTypeMapping(
RelationalTypeMappingParameters parameters,
SingleStoreDbType mySqlDbType,
- ISingleStoreOptions options)
- : base(parameters, mySqlDbType, options, false, false)
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction)
+ : base(
+ parameters,
+ mySqlDbType,
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction,
+ isUnquoted: false,
+ forceToString: false)
{
- Options = options;
}
+ ///
+ /// Supports compiled models via ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.Create.
+ ///
+ protected abstract RelationalTypeMapping Clone(
+ bool? noBackslashEscapes = null,
+ bool? replaceLineBreaksWithCharFunction = null);
+
protected override void ConfigureParameter(DbParameter parameter)
{
base.ConfigureParameter(parameter);
@@ -94,5 +118,55 @@ protected override void ConfigureParameter(DbParameter parameter)
parameter.Value = (string)mySqlJsonString;
}
}
+
+ void ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.Create(
+ CSharpRuntimeAnnotationCodeGeneratorParameters codeGeneratorParameters,
+ CSharpRuntimeAnnotationCodeGeneratorDependencies codeGeneratorDependencies)
+ {
+ var defaultTypeMapping = Default;
+ if (defaultTypeMapping == this)
+ {
+ return;
+ }
+
+ var code = codeGeneratorDependencies.CSharpHelper;
+
+ var cloneParameters = new List();
+
+ if (NoBackslashEscapes != defaultTypeMapping.NoBackslashEscapes)
+ {
+ cloneParameters.Add($"noBackslashEscapes: {code.Literal(NoBackslashEscapes)}");
+ }
+
+ if (ReplaceLineBreaksWithCharFunction != defaultTypeMapping.ReplaceLineBreaksWithCharFunction)
+ {
+ cloneParameters.Add($"replaceLineBreaksWithCharFunction: {code.Literal(ReplaceLineBreaksWithCharFunction)}");
+ }
+
+ if (cloneParameters.Any())
+ {
+ var mainBuilder = codeGeneratorParameters.MainBuilder;
+
+ mainBuilder.AppendLine(";");
+
+ mainBuilder
+ .AppendLine($"{codeGeneratorParameters.TargetName}.TypeMapping = (({code.Reference(GetType())}){codeGeneratorParameters.TargetName}.TypeMapping).Clone(")
+ .IncrementIndent();
+
+ for (var i = 0; i < cloneParameters.Count; i++)
+ {
+ if (i > 0)
+ {
+ mainBuilder.AppendLine(",");
+ }
+
+ mainBuilder.Append(cloneParameters[i]);
+ }
+
+ mainBuilder
+ .Append(")")
+ .DecrementIndent();
+ }
+ }
}
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMappingSourcePlugin.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMappingSourcePlugin.cs
index 1a3f13dec..5d0e1487d 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMappingSourcePlugin.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreJsonTypeMappingSourcePlugin.cs
@@ -44,7 +44,8 @@ public virtual RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo ma
storeTypeName,
GetValueConverter(clrType),
GetValueComparer(clrType),
- Options)
+ Options.NoBackslashEscapes,
+ Options.ReplaceLineBreaksWithCharFunction)
: null;
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreLongTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreLongTypeMapping.cs
new file mode 100644
index 000000000..ce0fecd82
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreLongTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreLongTypeMapping : LongTypeMapping
+{
+ public static new SingleStoreLongTypeMapping Default { get; } = new("bigint");
+
+ public SingleStoreLongTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.Int64)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreLongTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreLongTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs
index d4d692344..1c20ba5f0 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs
@@ -12,6 +12,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.Extensions.DependencyInjection;
using SingleStoreConnector;
using EntityFrameworkCore.SingleStore.Infrastructure;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
@@ -20,25 +21,140 @@ namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
public class SingleStoreRelationalConnection : RelationalConnection, ISingleStoreRelationalConnection
{
+ private readonly ISingleStoreConnectionStringOptionsValidator _mySqlConnectionStringOptionsValidator;
private const string NoBackslashEscapes = "NO_BACKSLASH_ESCAPES";
private readonly SingleStoreOptionsExtension _mySqlOptionsExtension;
+ private DbDataSource _dataSource;
+
+ public SingleStoreRelationalConnection(
+ RelationalConnectionDependencies dependencies,
+ ISingleStoreConnectionStringOptionsValidator mySqlConnectionStringOptionsValidator,
+ ISingleStoreOptions mySqlSingletonOptions)
+ : this(
+ dependencies,
+ mySqlConnectionStringOptionsValidator,
+ GetEffectiveDataSource(mySqlSingletonOptions, dependencies.ContextOptions))
+ {
+ }
- // ReSharper disable once VirtualMemberCallInConstructor
- public SingleStoreRelationalConnection(RelationalConnectionDependencies dependencies)
+ public SingleStoreRelationalConnection(
+ RelationalConnectionDependencies dependencies,
+ ISingleStoreConnectionStringOptionsValidator mySqlConnectionStringOptionsValidator,
+ DbDataSource dataSource)
: base(dependencies)
{
- _mySqlOptionsExtension = Dependencies.ContextOptions.FindExtension() ?? new SingleStoreOptionsExtension();
+ _mySqlOptionsExtension = dependencies.ContextOptions.FindExtension() ??
+ new SingleStoreOptionsExtension();
+ _mySqlConnectionStringOptionsValidator = mySqlConnectionStringOptionsValidator;
+
+ if (dataSource is not null)
+ {
+ _mySqlConnectionStringOptionsValidator.EnsureMandatoryOptions(dataSource);
+
+ base.SetDbConnection(null, false);
+ base.ConnectionString = null;
+
+ _dataSource = dataSource;
+ }
+ else if (base.ConnectionString is { } connectionString)
+ {
+ // This branch works for both: connections and connection strings, because base.ConnectionString handles both cases
+ // appropriately.
+ if (_mySqlConnectionStringOptionsValidator.EnsureMandatoryOptions(ref connectionString))
+ {
+ try
+ {
+ base.ConnectionString = connectionString;
+ }
+ catch (Exception e)
+ {
+ _mySqlConnectionStringOptionsValidator.ThrowException(e);
+ }
+ }
+ }
}
+ ///
+ /// We allow users to either explicitly set a DbDataSource using our `SingleStoreOptionsExtensions` or by adding it as a service via DI
+ /// (`ApplicationServiceProvider`).
+ /// We don't set a DI injected service to the `SingleStoreOption.DbDataSource` property, because it might get cached by the service
+ /// collection cache, since no relevant property might have changed in the `SingleStoreOptionsExtension` instance. If we would create
+ /// a similar DbContext instance with a different service collection later, EF Core would provide us with the *same* `SingleStoreOptions`
+ /// instance (that was cached before) and we would use the old `DbDataSource` instance that we retrieved from the old
+ /// `ApplicationServiceProvider`.
+ /// Therefore, we check the `ISingleStoreOptions.DbDataSource` property and the current `ApplicationServiceProvider` at the time we
+ /// actually need the instance.
+ ///
+ protected static DbDataSource GetEffectiveDataSource(ISingleStoreOptions mySqlSingletonOptions, IDbContextOptions contextOptions)
+ => mySqlSingletonOptions.DataSource ??
+ contextOptions.FindExtension()?.ApplicationServiceProvider?.GetService();
+
// TODO: Remove, because we don't use it anywhere.
private bool IsMasterConnection { get; set; }
protected override DbConnection CreateDbConnection()
- => new SingleStoreConnection(AddConnectionStringOptions(new SingleStoreConnectionStringBuilder(ConnectionString!)).ConnectionString);
+ => _dataSource is not null
+ ? _dataSource.CreateConnection()
+ : new SingleStoreConnection(AddConnectionStringOptions(new SingleStoreConnectionStringBuilder(ConnectionString!)).ConnectionString);
+
+ public override string ConnectionString
+ {
+ get => _dataSource is null
+ ? base.ConnectionString
+ : _dataSource.ConnectionString;
+ set
+ {
+ _mySqlConnectionStringOptionsValidator.EnsureMandatoryOptions(ref value);
+ base.ConnectionString = value;
+
+ _dataSource = null;
+ }
+ }
+
+ public override void SetDbConnection(DbConnection value, bool contextOwnsConnection)
+ {
+ _mySqlConnectionStringOptionsValidator.EnsureMandatoryOptions(value);
+
+ base.SetDbConnection(value, contextOwnsConnection);
+ }
+
+ [AllowNull]
+ public new virtual SingleStoreConnection DbConnection
+ {
+ get => (SingleStoreConnection)base.DbConnection;
+ set
+ {
+ base.DbConnection = value;
+
+ _dataSource = null;
+ }
+ }
+
+ public virtual DbDataSource DbDataSource
+ {
+ get => _dataSource;
+ set
+ {
+ _mySqlConnectionStringOptionsValidator.EnsureMandatoryOptions(value);
+
+ if (value is not null)
+ {
+ DbConnection = null;
+ ConnectionString = null;
+ }
+
+ _dataSource = value;
+ }
+ }
public virtual ISingleStoreRelationalConnection CreateMasterConnection()
{
+ if (Dependencies.ContextOptions.FindExtension() is not { } mySqlOptions)
+ {
+ throw new InvalidOperationException($"{nameof(SingleStoreOptionsExtension)} not found in {nameof(CreateMasterConnection)}");
+ }
+
// Add master connection specific options.
var csb = new SingleStoreConnectionStringBuilder(ConnectionString!)
{
@@ -48,32 +164,29 @@ public virtual ISingleStoreRelationalConnection CreateMasterConnection()
csb = AddConnectionStringOptions(csb);
- var connectionString = csb.ConnectionString;
- var relationalOptions = RelationalOptionsExtension.Extract(Dependencies.ContextOptions);
+ var masterConnectionString = csb.ConnectionString;
// Apply modified connection string.
- relationalOptions = relationalOptions.Connection is null
- ? relationalOptions.WithConnectionString(connectionString)
- : relationalOptions.WithConnection(DbConnection.CloneWith(connectionString));
+ var masterMySqlOptions = _dataSource is not null
+ ? mySqlOptions.WithConnection(((SingleStoreConnection)CreateDbConnection()).CloneWith(masterConnectionString))
+ : mySqlOptions.Connection is null
+ ? mySqlOptions.WithConnectionString(masterConnectionString)
+ : mySqlOptions.WithConnection(DbConnection.CloneWith(masterConnectionString));
var optionsBuilder = new DbContextOptionsBuilder();
var optionsBuilderInfrastructure = (IDbContextOptionsBuilderInfrastructure)optionsBuilder;
- optionsBuilderInfrastructure.AddOrUpdateExtension(relationalOptions);
+ optionsBuilderInfrastructure.AddOrUpdateExtension(masterMySqlOptions);
- return new SingleStoreRelationalConnection(Dependencies with { ContextOptions = optionsBuilder.Options })
+ return new SingleStoreRelationalConnection(
+ Dependencies with { ContextOptions = optionsBuilder.Options },
+ _mySqlConnectionStringOptionsValidator,
+ dataSource: null)
{
IsMasterConnection = true
};
}
- [AllowNull]
- public new virtual SingleStoreConnection DbConnection
- {
- get => (SingleStoreConnection)base.DbConnection;
- set => base.DbConnection = value;
- }
-
protected virtual SingleStoreConnectionStringBuilder AddConnectionStringOptions(SingleStoreConnectionStringBuilder builder)
{
var program_version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreSByteTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreSByteTypeMapping.cs
new file mode 100644
index 000000000..2ff198c23
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreSByteTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreSByteTypeMapping : SByteTypeMapping
+{
+ public static new SingleStoreSByteTypeMapping Default { get; } = new("tinyint");
+
+ public SingleStoreSByteTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.SByte)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreSByteTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreSByteTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreShortTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreShortTypeMapping.cs
new file mode 100644
index 000000000..f953f2b2a
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreShortTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreShortTypeMapping : ShortTypeMapping
+{
+ public static new SingleStoreShortTypeMapping Default { get; } = new("smallint");
+
+ public SingleStoreShortTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.Int16)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreShortTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreShortTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreStringTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreStringTypeMapping.cs
index bb2ace671..35852391b 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreStringTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreStringTypeMapping.cs
@@ -3,12 +3,15 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Data.Common;
+using System.Linq;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
+using Microsoft.EntityFrameworkCore.Storage.Json;
using SingleStoreConnector;
-using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -16,32 +19,39 @@ namespace EntityFrameworkCore.SingleStore.Storage.Internal
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
- public class SingleStoreStringTypeMapping : SingleStoreTypeMapping
+ public class SingleStoreStringTypeMapping : SingleStoreTypeMapping, ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator
{
- private readonly bool _forceToString;
+ public static SingleStoreStringTypeMapping Default { get; } = new("varchar", StoreTypePostfix.Size);
+
private const int UnicodeMax = 4000;
private const int AnsiMax = 8000;
private readonly int _maxSpecificSize;
- private readonly ISingleStoreOptions _options;
+ public virtual bool NoBackslashEscapes { get; }
+ public virtual bool ReplaceLineBreaksWithCharFunction { get; }
public virtual bool IsUnquoted { get; }
+ public virtual bool ForceToString { get; }
+
public virtual bool IsNationalChar
=> StoreTypeNameBase.StartsWith("n", StringComparison.OrdinalIgnoreCase) &&
StoreTypeNameBase.Contains("char", StringComparison.OrdinalIgnoreCase);
public SingleStoreStringTypeMapping(
[NotNull] string storeType,
- ISingleStoreOptions options,
StoreTypePostfix storeTypePostfix,
bool unicode = true,
int? size = null,
bool fixedLength = false,
+ bool noBackslashEscapes = false,
+ bool replaceLineBreaksWithCharFunction = true,
bool unquoted = false,
bool forceToString = false)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(string)),
+ new CoreTypeMappingParameters(
+ typeof(string),
+ jsonValueReaderWriter: JsonStringReaderWriter.Instance),
storeType,
storeTypePostfix,
unicode
@@ -57,7 +67,8 @@ public SingleStoreStringTypeMapping(
fixedLength
? SingleStoreDbType.String
: SingleStoreDbType.VarString,
- options,
+ noBackslashEscapes,
+ replaceLineBreaksWithCharFunction,
unquoted,
forceToString)
{
@@ -70,14 +81,16 @@ public SingleStoreStringTypeMapping(
protected SingleStoreStringTypeMapping(
RelationalTypeMappingParameters parameters,
SingleStoreDbType mySqlDbType,
- ISingleStoreOptions options,
+ bool noBackslashEscapes,
+ bool replaceLineBreaksWithCharFunction,
bool isUnquoted,
bool forceToString)
: base(parameters, mySqlDbType)
{
_maxSpecificSize = CalculateSize(parameters.Unicode, parameters.Size);
- _options = options;
- _forceToString = forceToString;
+ NoBackslashEscapes = noBackslashEscapes;
+ ReplaceLineBreaksWithCharFunction = replaceLineBreaksWithCharFunction;
+ ForceToString = forceToString;
IsUnquoted = isUnquoted;
}
@@ -92,10 +105,20 @@ private static int CalculateSize(bool unicode, int? size)
/// The parameters for this mapping.
/// The newly created mapping.
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new SingleStoreStringTypeMapping(parameters, SingleStoreDbType, _options, IsUnquoted, _forceToString);
+ => new SingleStoreStringTypeMapping(parameters, SingleStoreDbType, NoBackslashEscapes, ReplaceLineBreaksWithCharFunction, IsUnquoted, ForceToString);
- public virtual RelationalTypeMapping Clone(bool? unquoted = null, bool? forceToString = null)
- => new SingleStoreStringTypeMapping(Parameters, SingleStoreDbType, _options, unquoted ?? IsUnquoted, forceToString ?? _forceToString);
+ public virtual RelationalTypeMapping Clone(
+ bool? unquoted = null,
+ bool? forceToString = null,
+ bool? noBackslashEscapes = null,
+ bool? replaceLineBreaksWithCharFunction = null)
+ => new SingleStoreStringTypeMapping(
+ Parameters,
+ SingleStoreDbType,
+ noBackslashEscapes ?? NoBackslashEscapes,
+ replaceLineBreaksWithCharFunction ?? ReplaceLineBreaksWithCharFunction,
+ unquoted ?? IsUnquoted,
+ forceToString ?? ForceToString);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -109,7 +132,7 @@ protected override void ConfigureParameter(DbParameter parameter)
// -1 (unbounded) to avoid size inference.
var value = parameter.Value;
- if (_forceToString && value != null && value != DBNull.Value)
+ if (ForceToString && value != null && value != DBNull.Value)
{
value = value.ToString();
}
@@ -140,13 +163,13 @@ protected override void ConfigureParameter(DbParameter parameter)
protected override string GenerateNonNullSqlLiteral(object value)
{
- var stringValue = _forceToString
+ var stringValue = ForceToString
? value.ToString()
: (string)value;
return IsUnquoted
- ? EscapeSqlLiteral(stringValue, !_options.NoBackslashEscapes)
- : EscapeSqlLiteralWithLineBreaks(stringValue, !_options.NoBackslashEscapes, _options.ReplaceLineBreaksWithCharFunction);
+ ? EscapeSqlLiteral(stringValue, !NoBackslashEscapes)
+ : EscapeSqlLiteralWithLineBreaks(stringValue, !NoBackslashEscapes, ReplaceLineBreaksWithCharFunction);
}
public static string EscapeSqlLiteralWithLineBreaks(string value, bool escapeBackslashes, bool replaceLineBreaksWithCharFunction)
@@ -180,5 +203,65 @@ public static string EscapeBackslashes(string literal, bool escapeBackslashes)
? literal.Replace(@"\", @"\\")
: literal;
}
+
+ void ISingleStoreCSharpRuntimeAnnotationTypeMappingCodeGenerator.Create(
+ CSharpRuntimeAnnotationCodeGeneratorParameters codeGeneratorParameters,
+ CSharpRuntimeAnnotationCodeGeneratorDependencies codeGeneratorDependencies)
+ {
+ var defaultTypeMapping = Default;
+ if (defaultTypeMapping == this)
+ {
+ return;
+ }
+
+ var code = codeGeneratorDependencies.CSharpHelper;
+
+ var cloneParameters = new List();
+
+ if (IsUnquoted != defaultTypeMapping.IsUnquoted)
+ {
+ cloneParameters.Add($"unquoted: {code.Literal(IsUnquoted)}");
+ }
+
+ if (ForceToString != defaultTypeMapping.ForceToString)
+ {
+ cloneParameters.Add($"forceToString: {code.Literal(ForceToString)}");
+ }
+
+ if (NoBackslashEscapes != defaultTypeMapping.NoBackslashEscapes)
+ {
+ cloneParameters.Add($"noBackslashEscapes: {code.Literal(NoBackslashEscapes)}");
+ }
+
+ if (ReplaceLineBreaksWithCharFunction != defaultTypeMapping.ReplaceLineBreaksWithCharFunction)
+ {
+ cloneParameters.Add($"replaceLineBreaksWithCharFunction: {code.Literal(ReplaceLineBreaksWithCharFunction)}");
+ }
+
+ if (cloneParameters.Any())
+ {
+ var mainBuilder = codeGeneratorParameters.MainBuilder;
+
+ mainBuilder.AppendLine(";");
+
+ mainBuilder
+ .AppendLine($"{codeGeneratorParameters.TargetName}.TypeMapping = (({code.Reference(GetType())}){codeGeneratorParameters.TargetName}.TypeMapping).Clone(")
+ .IncrementIndent();
+
+ for (var i = 0; i < cloneParameters.Count; i++)
+ {
+ if (i > 0)
+ {
+ mainBuilder.AppendLine(",");
+ }
+
+ mainBuilder.Append(cloneParameters[i]);
+ }
+
+ mainBuilder
+ .Append(")")
+ .DecrementIndent();
+ }
+ }
}
}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreTimeTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreTimeTypeMapping.cs
index f13466d1d..af94921e2 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreTimeTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreTimeTypeMapping.cs
@@ -6,6 +6,7 @@
using System.Globalization;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
@@ -19,6 +20,8 @@ public class SingleStoreTimeTypeMapping : RelationalTypeMapping, IDefaultValueCo
{
private readonly bool _isDefaultValueCompatible;
+ public static SingleStoreTimeTypeMapping Default { get; } = new("time", typeof(TimeOnly));
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -30,7 +33,9 @@ public SingleStoreTimeTypeMapping(
bool isDefaultValueCompatible = true)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(clrType),
+ new CoreTypeMappingParameters(
+ clrType,
+ jsonValueReaderWriter: JsonTimeOnlyReaderWriter.Instance),
storeType,
StoreTypePostfix.Precision,
System.Data.DbType.Time,
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMapping.cs
index cb644ee72..0c2ebfc6d 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMapping.cs
@@ -8,6 +8,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SingleStoreConnector;
@@ -21,7 +22,7 @@ namespace EntityFrameworkCore.SingleStore.Storage.Internal
public abstract class SingleStoreTypeMapping : RelationalTypeMapping
{
///
- /// The database type used by SingleStore.
+ /// The database type used by SingleStore Distributed.
///
public virtual SingleStoreDbType SingleStoreDbType { get; }
@@ -34,10 +35,20 @@ public SingleStoreTypeMapping(
bool unicode = false,
int? size = null,
ValueConverter valueConverter = null,
- ValueComparer valueComparer = null)
+ ValueComparer valueComparer = null,
+ JsonValueReaderWriter jsonValueReaderWriter = null)
: base(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(clrType, valueConverter, valueComparer), storeType, StoreTypePostfix.None, dbType, unicode, size))
+ new CoreTypeMappingParameters(
+ clrType,
+ valueConverter,
+ valueComparer,
+ jsonValueReaderWriter: jsonValueReaderWriter),
+ storeType,
+ StoreTypePostfix.None,
+ dbType,
+ unicode,
+ size))
=> SingleStoreDbType = mySqlDbType;
///
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMappingSource.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMappingSource.cs
index ff64cdcc0..0e9faa828 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMappingSource.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreTypeMappingSource.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
-using System.Data;
using System.Linq;
using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using JetBrains.Annotations;
@@ -20,29 +19,29 @@ public class SingleStoreTypeMappingSource : RelationalTypeMappingSource
{
// boolean
private readonly SingleStoreBoolTypeMapping _bit1 = new SingleStoreBoolTypeMapping("bit", size: 1);
- private readonly SingleStoreBoolTypeMapping _tinyint1 = new SingleStoreBoolTypeMapping("tinyint", size: 1);
+ private readonly SingleStoreBoolTypeMapping _tinyint1 = SingleStoreBoolTypeMapping.Default;
// bit
- private readonly ULongTypeMapping _bit = new ULongTypeMapping("bit", DbType.UInt64);
+ private readonly SingleStoreULongTypeMapping _bit = new SingleStoreULongTypeMapping("bit");
// integers
- private readonly SByteTypeMapping _tinyint = new SByteTypeMapping("tinyint", DbType.SByte);
- private readonly ByteTypeMapping _utinyint = new ByteTypeMapping("tinyint unsigned", DbType.Byte);
- private readonly ShortTypeMapping _smallint = new ShortTypeMapping("smallint", DbType.Int16);
- private readonly UShortTypeMapping _usmallint = new UShortTypeMapping("smallint unsigned", DbType.UInt16);
- private readonly IntTypeMapping _int = new IntTypeMapping("int", DbType.Int32);
- private readonly UIntTypeMapping _uint = new UIntTypeMapping("int unsigned", DbType.UInt32);
- private readonly LongTypeMapping _bigint = new LongTypeMapping("bigint", DbType.Int64);
- private readonly ULongTypeMapping _ubigint = new ULongTypeMapping("bigint unsigned", DbType.UInt64);
+ private readonly SingleStoreSByteTypeMapping _tinyint = SingleStoreSByteTypeMapping.Default;
+ private readonly SingleStoreByteTypeMapping _utinyint = SingleStoreByteTypeMapping.Default;
+ private readonly SingleStoreShortTypeMapping _smallint = SingleStoreShortTypeMapping.Default;
+ private readonly SingleStoreUShortTypeMapping _usmallint = SingleStoreUShortTypeMapping.Default;
+ private readonly SingleStoreIntTypeMapping _int = SingleStoreIntTypeMapping.Default;
+ private readonly SingleStoreUIntTypeMapping _uint = SingleStoreUIntTypeMapping.Default;
+ private readonly SingleStoreLongTypeMapping _bigint = SingleStoreLongTypeMapping.Default;
+ private readonly SingleStoreULongTypeMapping _ubigint = SingleStoreULongTypeMapping.Default;
// decimals
- private readonly SingleStoreDecimalTypeMapping _decimal = new SingleStoreDecimalTypeMapping("decimal", precision: 65, scale: 30);
- private readonly SingleStoreDoubleTypeMapping _double = new SingleStoreDoubleTypeMapping("double", DbType.Double);
- private readonly SingleStoreFloatTypeMapping _float = new SingleStoreFloatTypeMapping("float", DbType.Single);
+ private readonly SingleStoreDecimalTypeMapping _decimal = SingleStoreDecimalTypeMapping.Default;
+ private readonly SingleStoreDoubleTypeMapping _double = SingleStoreDoubleTypeMapping.Default;
+ private readonly SingleStoreFloatTypeMapping _float = SingleStoreFloatTypeMapping.Default;
// binary
private readonly RelationalTypeMapping _binary = new SingleStoreByteArrayTypeMapping(fixedLength: true);
- private readonly RelationalTypeMapping _varbinary = new SingleStoreByteArrayTypeMapping();
+ private readonly RelationalTypeMapping _varbinary = SingleStoreByteArrayTypeMapping.Default;
//
// String mappings depend on the SingleStoreOptions.NoBackslashEscapes setting:
@@ -61,14 +60,14 @@ public class SingleStoreTypeMappingSource : RelationalTypeMappingSource
private SingleStoreStringTypeMapping _enum;
// DateTime
- private readonly SingleStoreYearTypeMapping _year = new SingleStoreYearTypeMapping("year");
- private readonly SingleStoreDateTypeMapping _dateDateOnly = new SingleStoreDateTypeMapping("date", typeof(DateOnly));
+ private readonly SingleStoreYearTypeMapping _year = SingleStoreYearTypeMapping.Default;
+ private readonly SingleStoreDateTypeMapping _dateDateOnly = SingleStoreDateTypeMapping.Default;
private readonly SingleStoreDateTypeMapping _dateDateTime = new SingleStoreDateTypeMapping("date", typeof(DateTime));
- private readonly SingleStoreTimeTypeMapping _timeTimeOnly = new SingleStoreTimeTypeMapping("time", typeof(TimeOnly));
+ private readonly SingleStoreTimeTypeMapping _timeTimeOnly = SingleStoreTimeTypeMapping.Default;
private readonly SingleStoreTimeTypeMapping _timeTimeSpan = new SingleStoreTimeTypeMapping("time", typeof(TimeSpan));
- private readonly SingleStoreDateTimeTypeMapping _dateTime = new SingleStoreDateTimeTypeMapping("datetime");
+ private readonly SingleStoreDateTimeTypeMapping _dateTime = SingleStoreDateTimeTypeMapping.Default;
private readonly SingleStoreDateTimeTypeMapping _timeStamp = new SingleStoreDateTimeTypeMapping("timestamp");
- private readonly SingleStoreDateTimeOffsetTypeMapping _dateTimeOffset = new SingleStoreDateTimeOffsetTypeMapping("datetime");
+ private readonly SingleStoreDateTimeOffsetTypeMapping _dateTimeOffset = SingleStoreDateTimeOffsetTypeMapping.Default;
private readonly SingleStoreDateTimeOffsetTypeMapping _timeStampOffset = new SingleStoreDateTimeOffsetTypeMapping("timestamp");
private readonly RelationalTypeMapping _binaryRowVersion
@@ -93,8 +92,8 @@ private readonly RelationalTypeMapping _binaryRowVersion6
private SingleStoreJsonTypeMapping _jsonDefaultString;
// Scaffolding type mappings
- private readonly SingleStoreCodeGenerationMemberAccessTypeMapping _codeGenerationMemberAccess = new SingleStoreCodeGenerationMemberAccessTypeMapping();
- private readonly SingleStoreCodeGenerationServerVersionCreationTypeMapping _codeGenerationServerVersionCreation = new SingleStoreCodeGenerationServerVersionCreationTypeMapping();
+ private readonly SingleStoreCodeGenerationMemberAccessTypeMapping _codeGenerationMemberAccess = SingleStoreCodeGenerationMemberAccessTypeMapping.Default;
+ private readonly SingleStoreCodeGenerationServerVersionCreationTypeMapping _codeGenerationServerVersionCreation = SingleStoreCodeGenerationServerVersionCreationTypeMapping.Default;
private Dictionary _storeTypeMappings;
private Dictionary _clrTypeMappings;
@@ -120,23 +119,23 @@ private void Initialize()
// String mappings depend on the SingleStoreOptions.NoBackslashEscapes setting:
//
- _charUnicode = new SingleStoreStringTypeMapping("char", _options, StoreTypePostfix.Size, fixedLength: true);
- _varcharUnicode = new SingleStoreStringTypeMapping("varchar", _options, StoreTypePostfix.Size);
- _tinytextUnicode = new SingleStoreStringTypeMapping("tinytext", _options, StoreTypePostfix.None);
- _textUnicode = new SingleStoreStringTypeMapping("text", _options, StoreTypePostfix.None);
- _mediumtextUnicode = new SingleStoreStringTypeMapping("mediumtext", _options, StoreTypePostfix.None);
- _longtextUnicode = new SingleStoreStringTypeMapping("longtext", _options, StoreTypePostfix.None);
+ _charUnicode = new SingleStoreStringTypeMapping("char", StoreTypePostfix.Size, fixedLength: true, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
+ _varcharUnicode = new SingleStoreStringTypeMapping("varchar", StoreTypePostfix.Size, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
+ _tinytextUnicode = new SingleStoreStringTypeMapping("tinytext", StoreTypePostfix.None, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
+ _textUnicode = new SingleStoreStringTypeMapping("text", StoreTypePostfix.None, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
+ _mediumtextUnicode = new SingleStoreStringTypeMapping("mediumtext", StoreTypePostfix.None, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
+ _longtextUnicode = new SingleStoreStringTypeMapping("longtext", StoreTypePostfix.None, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
- _nchar = new SingleStoreStringTypeMapping("nchar", _options, StoreTypePostfix.Size, fixedLength: true);
- _nvarchar = new SingleStoreStringTypeMapping("nvarchar", _options, StoreTypePostfix.Size);
+ _nchar = new SingleStoreStringTypeMapping("nchar", StoreTypePostfix.Size, fixedLength: true, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
+ _nvarchar = new SingleStoreStringTypeMapping("nvarchar", StoreTypePostfix.Size, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
- _enum = new SingleStoreStringTypeMapping("enum", _options, StoreTypePostfix.None);
+ _enum = new SingleStoreStringTypeMapping("enum", StoreTypePostfix.None, noBackslashEscapes: _options.NoBackslashEscapes, replaceLineBreaksWithCharFunction: _options.ReplaceLineBreaksWithCharFunction);
_guid = SingleStoreGuidTypeMapping.IsValidGuidFormat(_options.ConnectionSettings.GuidFormat)
? new SingleStoreGuidTypeMapping(_options.ConnectionSettings.GuidFormat)
: null;
- _jsonDefaultString = new SingleStoreJsonTypeMapping("json", null, null, _options);
+ _jsonDefaultString = new SingleStoreJsonTypeMapping("json", null, null, _options.NoBackslashEscapes, _options.ReplaceLineBreaksWithCharFunction);
_storeTypeMappings
= new Dictionary(StringComparer.OrdinalIgnoreCase)
@@ -227,24 +226,24 @@ private void Initialize()
// datetimes
{ typeof(DateOnly), _dateDateOnly },
- { typeof(TimeOnly), _timeTimeOnly.Clone(_options.DefaultDataTypeMappings.ClrTimeOnlyPrecision, null) },
+ { typeof(TimeOnly), _timeTimeOnly.WithPrecisionAndScale(_options.DefaultDataTypeMappings.ClrTimeOnlyPrecision, null) },
{ typeof(TimeSpan), _options.DefaultDataTypeMappings.ClrTimeSpan switch
{
- SingleStoreTimeSpanType.Time6 => _timeTimeSpan.Clone(6, null),
+ SingleStoreTimeSpanType.Time6 => _timeTimeSpan.WithPrecisionAndScale(6, null),
SingleStoreTimeSpanType.Time => _timeTimeSpan,
_ => _timeTimeSpan
}},
{ typeof(DateTime), _options.DefaultDataTypeMappings.ClrDateTime switch
{
- SingleStoreDateTimeType.DateTime6 =>_dateTime.Clone(6, null),
- SingleStoreDateTimeType.Timestamp6 => _timeStamp.Clone(6, null),
+ SingleStoreDateTimeType.DateTime6 =>_dateTime.WithPrecisionAndScale(6, null),
+ SingleStoreDateTimeType.Timestamp6 => _timeStamp.WithPrecisionAndScale(6, null),
SingleStoreDateTimeType.Timestamp => _timeStamp,
_ => _dateTime,
}},
{ typeof(DateTimeOffset), _options.DefaultDataTypeMappings.ClrDateTimeOffset switch
{
- SingleStoreDateTimeType.DateTime6 =>_dateTimeOffset.Clone(6, null),
- SingleStoreDateTimeType.Timestamp6 => _timeStampOffset.Clone(6, null),
+ SingleStoreDateTimeType.DateTime6 =>_dateTimeOffset.WithPrecisionAndScale(6, null),
+ SingleStoreDateTimeType.Timestamp6 => _timeStampOffset.WithPrecisionAndScale(6, null),
SingleStoreDateTimeType.Timestamp => _timeStampOffset,
_ => _dateTimeOffset,
}},
@@ -327,9 +326,9 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
{
return clrType == null
? mappings[0]
- .Clone(in mappingInfo)
+ .WithTypeMappingInfo(in mappingInfo)
: mappings.FirstOrDefault(m => m.ClrType == clrType)
- ?.Clone(in mappingInfo);
+ ?.WithTypeMappingInfo(in mappingInfo);
}
if (storeTypeName.Equals("json", StringComparison.OrdinalIgnoreCase) &&
@@ -351,14 +350,14 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
{
if (clrType == typeof(decimal))
{
- return mapping.Clone(mappingInfo.Precision.Value, mappingInfo.Scale);
+ return mapping.WithPrecisionAndScale(mappingInfo.Precision.Value, mappingInfo.Scale);
}
if (clrType == typeof(DateTime) ||
clrType == typeof(DateTimeOffset) ||
clrType == typeof(TimeSpan))
{
- return mapping.Clone(mappingInfo.Precision.Value, null);
+ return mapping.WithPrecisionAndScale(mappingInfo.Precision.Value, null);
}
}
@@ -384,11 +383,15 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
// If a string column size is bigger than it can/might be, we automatically adjust it to a variable one with an
// unlimited size.
- if (size > 65_553 / _options.DefaultCharSet.MaxBytesPerChar)
+ if (size > 65_535 / _options.DefaultCharSet.MaxBytesPerChar)
{
size = null;
isFixedLength = false;
}
+ else if (size < 0) // specifying HasMaxLength(-1) is valid and should lead to an unbounded string/text.
+ {
+ size = null;
+ }
mapping = isFixedLength
? _charUnicode
@@ -398,7 +401,7 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
return size == null
? mapping
- : mapping.Clone($"{mapping.StoreTypeNameBase}({size})", size);
+ : mapping.WithStoreTypeAndSize($"{mapping.StoreTypeNameBase}({size})", size);
}
if (clrType == typeof(byte[]))
@@ -415,6 +418,12 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
? _options.ServerVersion.MaxKeyLength
: (int?)null);
+ // Specifying HasMaxLength(-1) is valid and should lead to an unbounded byte array/blob.
+ if (size < 0)
+ {
+ size = null;
+ }
+
return new SingleStoreByteArrayTypeMapping(
size: size,
fixedLength: mappingInfo.IsFixedLength == true);
@@ -429,9 +438,14 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
return null;
}
- protected override string ParseStoreTypeName(string storeTypeName, out bool? unicode, out int? size, out int? precision, out int? scale)
+ protected override string ParseStoreTypeName(
+ string storeTypeName,
+ ref bool? unicode,
+ ref int? size,
+ ref int? precision,
+ ref int? scale)
{
- var storeTypeBaseName = base.ParseStoreTypeName(storeTypeName, out unicode, out size, out precision, out scale);
+ var storeTypeBaseName = base.ParseStoreTypeName(storeTypeName, ref unicode, ref size, ref precision, ref scale);
if (storeTypeBaseName is not null)
{
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreUIntTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreUIntTypeMapping.cs
new file mode 100644
index 000000000..7326d5a60
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreUIntTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreUIntTypeMapping : UIntTypeMapping
+{
+ public static new SingleStoreUIntTypeMapping Default { get; } = new("int unsigned");
+
+ public SingleStoreUIntTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.UInt32)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreUIntTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreUIntTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreULongTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreULongTypeMapping.cs
new file mode 100644
index 000000000..0633000d3
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreULongTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreULongTypeMapping : ULongTypeMapping
+{
+ public static new SingleStoreULongTypeMapping Default { get; } = new("bigint unsigned");
+
+ public SingleStoreULongTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.UInt64)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreULongTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreULongTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreUShortTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreUShortTypeMapping.cs
new file mode 100644
index 000000000..fe236a147
--- /dev/null
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreUShortTypeMapping.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) SingleStore Inc. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Data;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace EntityFrameworkCore.SingleStore.Storage.Internal;
+
+public class SingleStoreUShortTypeMapping : UShortTypeMapping
+{
+ public static new SingleStoreUShortTypeMapping Default { get; } = new("smallint unsigned");
+
+ public SingleStoreUShortTypeMapping(
+ string storeType,
+ DbType? dbType = System.Data.DbType.UInt16)
+ : base(storeType, dbType)
+ {
+ }
+
+ protected SingleStoreUShortTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SingleStoreUShortTypeMapping(parameters);
+}
diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreYearTypeMapping.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreYearTypeMapping.cs
index 9814ee134..757cff412 100644
--- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreYearTypeMapping.cs
+++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreYearTypeMapping.cs
@@ -4,18 +4,22 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
using SingleStoreConnector;
namespace EntityFrameworkCore.SingleStore.Storage.Internal
{
public class SingleStoreYearTypeMapping : SingleStoreTypeMapping
{
+ public static SingleStoreYearTypeMapping Default { get; } = new("year");
+
public SingleStoreYearTypeMapping([NotNull] string storeType)
: base(
storeType,
typeof(short),
SingleStoreDbType.Year,
- System.Data.DbType.Int16)
+ System.Data.DbType.Int16,
+ jsonValueReaderWriter: JsonInt16ReaderWriter.Instance)
{
}
diff --git a/src/EFCore.SingleStore/Update/Internal/SingleStoreUpdateSqlGenerator.cs b/src/EFCore.SingleStore/Update/Internal/SingleStoreUpdateSqlGenerator.cs
index b9c70f4a3..30dee7d35 100644
--- a/src/EFCore.SingleStore/Update/Internal/SingleStoreUpdateSqlGenerator.cs
+++ b/src/EFCore.SingleStore/Update/Internal/SingleStoreUpdateSqlGenerator.cs
@@ -215,7 +215,7 @@ private static (string tableName, string schema) GetTableNameAndSchema(IColumnMo
{
// CHECK: Is this branch ever hit and then returns something different than null, or can we just rely on
// `modification.Column?.Table`?
- return (property.DeclaringEntityType.GetTableName(), property.DeclaringEntityType.GetSchema());
+ return (property.DeclaringType.GetTableName(), property.DeclaringType.GetSchema());
}
}
}
diff --git a/src/EFCore.SingleStore/Utilities/ByteArrayFormatter.cs b/src/EFCore.SingleStore/Utilities/ByteArrayFormatter.cs
deleted file mode 100644
index 7c9cc60cd..000000000
--- a/src/EFCore.SingleStore/Utilities/ByteArrayFormatter.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) Pomelo Foundation. All rights reserved.
-// Copyright (c) SingleStore Inc. All rights reserved.
-// Licensed under the MIT. See LICENSE in the project root for license information.
-
-using System.Text;
-using JetBrains.Annotations;
-
-namespace EntityFrameworkCore.SingleStore.Utilities
-{
- internal static class ByteArrayFormatter
- {
- private static readonly char[] _lookup = new char[16]
- {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
- };
-
- public static string ToHex([NotNull] byte[] b)
- {
- if (b.Length == 0)
- {
- return "X''";
- }
-
- var builder = new StringBuilder("0x", 2 + (b.Length * 2));
- for (var i = 0; i < b.Length; i++)
- {
- var b1 = (byte)(b[i] >> 4);
- var b2 = (byte)(b[i] & 0xF);
- builder.Append(_lookup[b1]);
- builder.Append(_lookup[b2]);
- }
- return builder.ToString();
- }
- }
-}
diff --git a/src/EFCore.SingleStore/ValueGeneration/Internal/SingleStoreValueGeneratorSelector.cs b/src/EFCore.SingleStore/ValueGeneration/Internal/SingleStoreValueGeneratorSelector.cs
index 5a80f6b71..b12e8b47a 100644
--- a/src/EFCore.SingleStore/ValueGeneration/Internal/SingleStoreValueGeneratorSelector.cs
+++ b/src/EFCore.SingleStore/ValueGeneration/Internal/SingleStoreValueGeneratorSelector.cs
@@ -31,17 +31,14 @@ public SingleStoreValueGeneratorSelector(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override ValueGenerator Create(IProperty property, IEntityType entityType)
+ protected override ValueGenerator FindForType(IProperty property, ITypeBase typeBase, Type clrType)
{
- Check.NotNull(property, nameof(property));
- Check.NotNull(entityType, nameof(entityType));
-
- var ret = property.ClrType.UnwrapNullableType() == typeof(Guid)
+ var ret = clrType == typeof(Guid)
? property.ValueGenerated == ValueGenerated.Never
|| property.GetDefaultValueSql() != null
- ? (ValueGenerator)new TemporaryGuidValueGenerator()
+ ? new TemporaryGuidValueGenerator()
: new SingleStoreSequentialGuidValueGenerator(_options)
- : base.Create(property, entityType);
+ : base.FindForType(property, typeBase, clrType);
return ret;
}
}
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index 2592c4060..09501c267 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -13,5 +13,6 @@
+
diff --git a/test/EFCore.SingleStore.FunctionalTests/AssemblyInfo.cs b/test/EFCore.SingleStore.FunctionalTests/AssemblyInfo.cs
index 9205dc0df..6714ac906 100644
--- a/test/EFCore.SingleStore.FunctionalTests/AssemblyInfo.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/AssemblyInfo.cs
@@ -9,7 +9,7 @@
#if FIXED_TEST_ORDER
-[assembly: CollectionBehavior(DisableTestParallelization = true)]
+[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)]
[assembly: TestCollectionOrderer("EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities.Xunit.SingleStoreTestCollectionOrderer", "EntityFrameworkCore.SingleStore.FunctionalTests")]
[assembly: TestCaseOrderer("EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities.Xunit.SingleStoreTestCaseOrderer", "EntityFrameworkCore.SingleStore.FunctionalTests")]
diff --git a/test/EFCore.SingleStore.FunctionalTests/BuiltInDataTypesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BuiltInDataTypesSingleStoreTest.cs
index 012b572ad..97c9d2f83 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BuiltInDataTypesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BuiltInDataTypesSingleStoreTest.cs
@@ -1009,6 +1009,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInDataTypes.TestBoolean ---> [tinyint] [Precision = 3 Scale = 0]
BuiltInDataTypes.TestByte ---> [tinyint] [Precision = 3 Scale = 0]
BuiltInDataTypes.TestCharacter ---> [varchar] [MaxLength = 1]
+BuiltInDataTypes.TestDateOnly ---> [date]
BuiltInDataTypes.TestDateTime ---> [datetime] [Precision = 6]
BuiltInDataTypes.TestDateTimeOffset ---> [datetime] [Precision = 6]
BuiltInDataTypes.TestDecimal ---> [decimal] [Precision = 65 Scale = 30]
@@ -1018,6 +1019,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInDataTypes.TestInt64 ---> [bigint] [Precision = 19 Scale = 0]
BuiltInDataTypes.TestSignedByte ---> [tinyint] [Precision = 3 Scale = 0]
BuiltInDataTypes.TestSingle ---> [float] [Precision = 12]
+BuiltInDataTypes.TestTimeOnly ---> [time] [Precision = 6]
BuiltInDataTypes.TestTimeSpan ---> [time]
BuiltInDataTypes.TestUnsignedInt16 ---> [smallint] [Precision = 5 Scale = 0]
BuiltInDataTypes.TestUnsignedInt32 ---> [int] [Precision = 10 Scale = 0]
@@ -1035,6 +1037,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInDataTypesShadow.TestBoolean ---> [tinyint] [Precision = 3 Scale = 0]
BuiltInDataTypesShadow.TestByte ---> [tinyint] [Precision = 3 Scale = 0]
BuiltInDataTypesShadow.TestCharacter ---> [varchar] [MaxLength = 1]
+BuiltInDataTypesShadow.TestDateOnly ---> [date]
BuiltInDataTypesShadow.TestDateTime ---> [datetime] [Precision = 6]
BuiltInDataTypesShadow.TestDateTimeOffset ---> [datetime] [Precision = 6]
BuiltInDataTypesShadow.TestDecimal ---> [decimal] [Precision = 65 Scale = 30]
@@ -1044,6 +1047,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInDataTypesShadow.TestInt64 ---> [bigint] [Precision = 19 Scale = 0]
BuiltInDataTypesShadow.TestSignedByte ---> [tinyint] [Precision = 3 Scale = 0]
BuiltInDataTypesShadow.TestSingle ---> [float] [Precision = 12]
+BuiltInDataTypesShadow.TestTimeOnly ---> [time] [Precision = 6]
BuiltInDataTypesShadow.TestTimeSpan ---> [time]
BuiltInDataTypesShadow.TestUnsignedInt16 ---> [smallint] [Precision = 5 Scale = 0]
BuiltInDataTypesShadow.TestUnsignedInt32 ---> [int] [Precision = 10 Scale = 0]
@@ -1062,6 +1066,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInNullableDataTypes.TestNullableBoolean ---> [nullable tinyint] [Precision = 3 Scale = 0]
BuiltInNullableDataTypes.TestNullableByte ---> [nullable tinyint] [Precision = 3 Scale = 0]
BuiltInNullableDataTypes.TestNullableCharacter ---> [nullable varchar] [MaxLength = 1]
+BuiltInNullableDataTypes.TestNullableDateOnly ---> [nullable date]
BuiltInNullableDataTypes.TestNullableDateTime ---> [nullable datetime] [Precision = 6]
BuiltInNullableDataTypes.TestNullableDateTimeOffset ---> [nullable datetime] [Precision = 6]
BuiltInNullableDataTypes.TestNullableDecimal ---> [nullable decimal] [Precision = 65 Scale = 30]
@@ -1071,6 +1076,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInNullableDataTypes.TestNullableInt64 ---> [nullable bigint] [Precision = 19 Scale = 0]
BuiltInNullableDataTypes.TestNullableSignedByte ---> [nullable tinyint] [Precision = 3 Scale = 0]
BuiltInNullableDataTypes.TestNullableSingle ---> [nullable float] [Precision = 12]
+BuiltInNullableDataTypes.TestNullableTimeOnly ---> [nullable time] [Precision = 6]
BuiltInNullableDataTypes.TestNullableTimeSpan ---> [nullable time]
BuiltInNullableDataTypes.TestNullableUnsignedInt16 ---> [nullable smallint] [Precision = 5 Scale = 0]
BuiltInNullableDataTypes.TestNullableUnsignedInt32 ---> [nullable int] [Precision = 10 Scale = 0]
@@ -1090,6 +1096,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInNullableDataTypesShadow.TestNullableBoolean ---> [nullable tinyint] [Precision = 3 Scale = 0]
BuiltInNullableDataTypesShadow.TestNullableByte ---> [nullable tinyint] [Precision = 3 Scale = 0]
BuiltInNullableDataTypesShadow.TestNullableCharacter ---> [nullable varchar] [MaxLength = 1]
+BuiltInNullableDataTypesShadow.TestNullableDateOnly ---> [nullable date]
BuiltInNullableDataTypesShadow.TestNullableDateTime ---> [nullable datetime] [Precision = 6]
BuiltInNullableDataTypesShadow.TestNullableDateTimeOffset ---> [nullable datetime] [Precision = 6]
BuiltInNullableDataTypesShadow.TestNullableDecimal ---> [nullable decimal] [Precision = 65 Scale = 30]
@@ -1099,6 +1106,7 @@ public virtual void Columns_have_expected_data_types()
BuiltInNullableDataTypesShadow.TestNullableInt64 ---> [nullable bigint] [Precision = 19 Scale = 0]
BuiltInNullableDataTypesShadow.TestNullableSignedByte ---> [nullable tinyint] [Precision = 3 Scale = 0]
BuiltInNullableDataTypesShadow.TestNullableSingle ---> [nullable float] [Precision = 12]
+BuiltInNullableDataTypesShadow.TestNullableTimeOnly ---> [nullable time] [Precision = 6]
BuiltInNullableDataTypesShadow.TestNullableTimeSpan ---> [nullable time]
BuiltInNullableDataTypesShadow.TestNullableUnsignedInt16 ---> [nullable smallint] [Precision = 5 Scale = 0]
BuiltInNullableDataTypesShadow.TestNullableUnsignedInt32 ---> [nullable int] [Precision = 10 Scale = 0]
@@ -1192,9 +1200,11 @@ public virtual void Columns_have_expected_data_types()
MaxLengthDataTypes.Id ---> [int] [Precision = 10 Scale = 0]
MaxLengthDataTypes.String3 ---> [nullable varchar] [MaxLength = 3]
MaxLengthDataTypes.String9000 ---> [nullable varchar] [MaxLength = 9000]
+MaxLengthDataTypes.StringUnbounded ---> [nullable longtext] [MaxLength = -1]
NonNullableBackedDataTypes.Boolean ---> [nullable tinyint] [Precision = 3 Scale = 0]
NonNullableBackedDataTypes.Byte ---> [nullable tinyint] [Precision = 3 Scale = 0]
NonNullableBackedDataTypes.Character ---> [nullable varchar] [MaxLength = 1]
+NonNullableBackedDataTypes.DateOnly ---> [nullable date]
NonNullableBackedDataTypes.DateTime ---> [nullable datetime] [Precision = 6]
NonNullableBackedDataTypes.DateTimeOffset ---> [nullable datetime] [Precision = 6]
NonNullableBackedDataTypes.Decimal ---> [nullable decimal] [Precision = 65 Scale = 30]
@@ -1214,6 +1224,7 @@ public virtual void Columns_have_expected_data_types()
NonNullableBackedDataTypes.PartitionId ---> [int] [Precision = 10 Scale = 0]
NonNullableBackedDataTypes.SignedByte ---> [nullable tinyint] [Precision = 3 Scale = 0]
NonNullableBackedDataTypes.Single ---> [nullable float] [Precision = 12]
+NonNullableBackedDataTypes.TimeOnly ---> [nullable time] [Precision = 6]
NonNullableBackedDataTypes.TimeSpan ---> [nullable time]
NonNullableBackedDataTypes.UnsignedInt16 ---> [nullable smallint] [Precision = 5 Scale = 0]
NonNullableBackedDataTypes.UnsignedInt32 ---> [nullable int] [Precision = 10 Scale = 0]
@@ -1221,6 +1232,7 @@ public virtual void Columns_have_expected_data_types()
NullableBackedDataTypes.Boolean ---> [tinyint] [Precision = 3 Scale = 0]
NullableBackedDataTypes.Byte ---> [tinyint] [Precision = 3 Scale = 0]
NullableBackedDataTypes.Character ---> [varchar] [MaxLength = 1]
+NullableBackedDataTypes.DateOnly ---> [date]
NullableBackedDataTypes.DateTime ---> [datetime] [Precision = 6]
NullableBackedDataTypes.DateTimeOffset ---> [datetime] [Precision = 6]
NullableBackedDataTypes.Decimal ---> [decimal] [Precision = 65 Scale = 30]
@@ -1240,6 +1252,7 @@ public virtual void Columns_have_expected_data_types()
NullableBackedDataTypes.PartitionId ---> [int] [Precision = 10 Scale = 0]
NullableBackedDataTypes.SignedByte ---> [tinyint] [Precision = 3 Scale = 0]
NullableBackedDataTypes.Single ---> [float] [Precision = 12]
+NullableBackedDataTypes.TimeOnly ---> [time] [Precision = 6]
NullableBackedDataTypes.TimeSpan ---> [time]
NullableBackedDataTypes.UnsignedInt16 ---> [smallint] [Precision = 5 Scale = 0]
NullableBackedDataTypes.UnsignedInt32 ---> [int] [Precision = 10 Scale = 0]
@@ -1248,6 +1261,7 @@ public virtual void Columns_have_expected_data_types()
ObjectBackedDataTypes.Byte ---> [tinyint] [Precision = 3 Scale = 0]
ObjectBackedDataTypes.Bytes ---> [nullable longblob] [MaxLength = -1]
ObjectBackedDataTypes.Character ---> [varchar] [MaxLength = 1]
+ObjectBackedDataTypes.DateOnly ---> [date]
ObjectBackedDataTypes.DateTime ---> [datetime] [Precision = 6]
ObjectBackedDataTypes.DateTimeOffset ---> [datetime] [Precision = 6]
ObjectBackedDataTypes.Decimal ---> [decimal] [Precision = 65 Scale = 30]
@@ -1268,6 +1282,7 @@ public virtual void Columns_have_expected_data_types()
ObjectBackedDataTypes.SignedByte ---> [tinyint] [Precision = 3 Scale = 0]
ObjectBackedDataTypes.Single ---> [float] [Precision = 12]
ObjectBackedDataTypes.String ---> [nullable longtext] [MaxLength = 4294967295]
+ObjectBackedDataTypes.TimeOnly ---> [time] [Precision = 6]
ObjectBackedDataTypes.TimeSpan ---> [time]
ObjectBackedDataTypes.UnsignedInt16 ---> [smallint] [Precision = 5 Scale = 0]
ObjectBackedDataTypes.UnsignedInt32 ---> [int] [Precision = 10 Scale = 0]
@@ -1496,31 +1511,31 @@ public override void Can_insert_and_read_back_all_non_nullable_data_types()
}
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_binary_key()
{
base.Can_insert_and_read_back_with_binary_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_null_binary_foreign_key()
{
base.Can_insert_and_read_back_with_null_binary_foreign_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_null_string_foreign_key()
{
base.Can_insert_and_read_back_with_null_string_foreign_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_string_key()
{
base.Can_insert_and_read_back_with_string_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_read_back_mapped_enum_from_collection_first_or_default()
{
base.Can_read_back_mapped_enum_from_collection_first_or_default();
@@ -1881,6 +1896,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
});
MakeRequired(modelBuilder);
+
+ // MySQL supports a max. row size of 65535 bytes.
+ modelBuilder.Entity(
+ b =>
+ {
+ // Reset the max. length of `StringUnbounded` back to -1.
+ // Probably a bug in the Fluent API of the class, that it is first set to -1 and immediately afterwards to 9000.
+ b.Property(e => e.StringUnbounded).HasMaxLength(-1);
+ });
+
modelBuilder.Ignore();
modelBuilder.Ignore();
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSingleStoreTest.cs
new file mode 100644
index 000000000..6f2e0d782
--- /dev/null
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSingleStoreTest.cs
@@ -0,0 +1,131 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
+
+public class ComplexTypeBulkUpdatesSingleStoreTest : ComplexTypeBulkUpdatesTestBase<
+ ComplexTypeBulkUpdatesSingleStoreTest.ComplexTypeBulkUpdatesSingleStoreFixture>
+{
+ public ComplexTypeBulkUpdatesSingleStoreTest(ComplexTypeBulkUpdatesSingleStoreFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
+ {
+ }
+
+ public override async Task Delete_entity_type_with_complex_type(bool async)
+ {
+ await base.Delete_entity_type_with_complex_type(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Customer` AS `c`
+WHERE `c`.`Name` = 'Monty Elias'
+""");
+ }
+
+ public override async Task Delete_complex_type_throws(bool async)
+ {
+ await base.Delete_complex_type_throws(async);
+
+ AssertSql();
+ }
+
+ public override async Task Update_property_inside_complex_type(bool async)
+ {
+ await base.Update_property_inside_complex_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customer` AS `c`
+SET `c`.`ShippingAddress_ZipCode` = 12345
+WHERE `c`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Update_property_inside_nested_complex_type(bool async)
+ {
+ await base.Update_property_inside_nested_complex_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customer` AS `c`
+SET `c`.`ShippingAddress_Country_FullName` = 'United States Modified'
+WHERE `c`.`ShippingAddress_Country_Code` = 'US'
+""");
+ }
+
+ public override async Task Update_multiple_properties_inside_multiple_complex_types_and_on_entity_type(bool async)
+ {
+ await base.Update_multiple_properties_inside_multiple_complex_types_and_on_entity_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customer` AS `c`
+SET `c`.`BillingAddress_ZipCode` = 54321,
+ `c`.`ShippingAddress_ZipCode` = `c`.`BillingAddress_ZipCode`,
+ `c`.`Name` = CONCAT(`c`.`Name`, 'Modified')
+WHERE `c`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Update_projected_complex_type(bool async)
+ {
+ await base.Update_projected_complex_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customer` AS `c`
+SET `c`.`ShippingAddress_ZipCode` = 12345
+""");
+ }
+
+ public override async Task Update_multiple_projected_complex_types_via_anonymous_type(bool async)
+ {
+ await base.Update_multiple_projected_complex_types_via_anonymous_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customer` AS `c`
+SET `c`.`BillingAddress_ZipCode` = 54321,
+ `c`.`ShippingAddress_ZipCode` = `c`.`BillingAddress_ZipCode`
+""");
+ }
+
+ public override async Task Update_projected_complex_type_via_OrderBy_Skip_throws(bool async)
+ {
+ await base.Update_projected_complex_type_via_OrderBy_Skip_throws(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ {
+ TestHelpers.AssertAllMethodsOverridden(GetType());
+ }
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ {
+ Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+ }
+
+ private void AssertSql(params string[] expected)
+ {
+ Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+ }
+
+ protected void ClearLog()
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ }
+
+ public class ComplexTypeBulkUpdatesSingleStoreFixture : ComplexTypeBulkUpdatesFixtureBase
+ {
+ protected override ITestStoreFactory TestStoreFactory
+ => SingleStoreTestStoreFactory.Instance;
+ }
+}
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSingleStoreTest.cs
index a66f196bc..214d7a222 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSingleStoreTest.cs
@@ -1,7 +1,9 @@
+using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using Microsoft.EntityFrameworkCore;
using Xunit;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
@@ -40,19 +42,152 @@ public override async Task Delete_aggregate_root_when_table_sharing_with_non_own
AssertSql();
}
- public override async Task Delete_predicate_based_on_optional_navigation(bool async)
+ public override async Task Delete_entity_with_auto_include(bool async)
{
- await base.Delete_predicate_based_on_optional_navigation(async);
+ await base.Delete_entity_with_auto_include(async);
AssertSql(
"""
- DELETE `p`
- FROM `Posts` AS `p`
- LEFT JOIN `Blogs` AS `b` ON `p`.`BlogId` = `b`.`Id`
- WHERE `b`.`Title` IS NOT NULL AND (`b`.`Title` LIKE 'Arthur%')
+ DELETE `c`
+ FROM `Context30572_Principal` AS `c`
+ LEFT JOIN `Context30572_Dependent` AS `c0` ON `c`.`DependentId` = `c0`.`Id`
""");
}
+ public override async Task Delete_predicate_based_on_optional_navigation(bool async)
+ {
+ await base.Delete_predicate_based_on_optional_navigation(async);
+
+ AssertSql(
+"""
+DELETE `p`
+FROM `Posts` AS `p`
+LEFT JOIN `Blogs` AS `b` ON `p`.`BlogId` = `b`.`Id`
+WHERE `b`.`Title` LIKE 'Arthur%'
+""");
+ }
+
+ public override async Task Update_non_owned_property_on_entity_with_owned(bool async)
+ {
+ await base.Update_non_owned_property_on_entity_with_owned(async);
+
+ AssertSql(
+"""
+UPDATE `Owner` AS `o`
+SET `o`.`Title` = 'SomeValue'
+""");
+ }
+
+ public override async Task Update_non_owned_property_on_entity_with_owned2(bool async)
+ {
+ await base.Update_non_owned_property_on_entity_with_owned2(async);
+
+ AssertSql(
+"""
+UPDATE `Owner` AS `o`
+SET `o`.`Title` = CONCAT(COALESCE(`o`.`Title`, ''), '_Suffix')
+""");
+ }
+
+ public override async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async)
+ {
+ await base.Update_owned_and_non_owned_properties_with_table_sharing(async);
+
+ AssertSql(
+"""
+UPDATE `Owner` AS `o`
+SET `o`.`OwnedReference_Number` = CHAR_LENGTH(`o`.`Title`),
+ `o`.`Title` = CAST(`o`.`OwnedReference_Number` AS char)
+""");
+ }
+
+ public override async Task Update_main_table_in_entity_with_entity_splitting(bool async)
+ {
+ var contextFactory = await InitializeAsync(
+ onModelCreating: mb =>
+ {
+ mb.Entity(b =>
+ {
+ // Set column type
+ b.Property(p => p.Id).HasColumnType("bigint");
+
+ // Split entity across two tables
+ b.ToTable("Blogs")
+ .SplitToTable("BlogsPart1", tb =>
+ {
+ tb.Property(p => p.Title);
+ tb.Property(p => p.Rating);
+ });
+ });
+
+ mb.Entity(b =>
+ b.Property(p => p.Id).HasColumnType("bigint"));
+ },
+ seed: context =>
+ {
+ context.Set().Add(new Blog { Title = "SomeBlog" });
+ context.SaveChanges();
+ });
+
+ await AssertUpdate(
+ async,
+ contextFactory.CreateContext,
+ ss => ss.Set(),
+ s => s.SetProperty(b => b.CreationTimestamp, b => new DateTime(2020, 1, 1)),
+ rowsAffectedCount: 1);
+
+ AssertSql(
+"""
+UPDATE `Blogs` AS `b`
+SET `b`.`CreationTimestamp` = '2020-01-01 00:00:00'
+""");
+ }
+
+ public override async Task Update_non_main_table_in_entity_with_entity_splitting(bool async)
+ {
+ var contextFactory = await InitializeAsync(
+ onModelCreating: mb =>
+ {
+ mb.Entity(b =>
+ {
+ // Set column type
+ b.Property(p => p.Id).HasColumnType("bigint");
+
+ // Split entity across two tables
+ b.ToTable("Blogs")
+ .SplitToTable("BlogsPart1", tb =>
+ {
+ tb.Property(p => p.Title);
+ tb.Property(p => p.Rating);
+ });
+ });
+
+ mb.Entity(b =>
+ b.Property(p => p.Id).HasColumnType("bigint"));
+ },
+ seed: context =>
+ {
+ context.Set().Add(new Blog { Title = "SomeBlog" });
+ context.SaveChanges();
+ });
+
+ await AssertUpdate(
+ async,
+ contextFactory.CreateContext,
+ ss => ss.Set(),
+ s => s
+ .SetProperty(b => b.Title, b => b.Rating.ToString())
+ .SetProperty(b => b.Rating, b => b.Title!.Length),
+ rowsAffectedCount: 1);
+
+ AssertSql(
+"""
+UPDATE `BlogsPart1` AS `b0`
+SET `b0`.`Rating` = CHAR_LENGTH(`b0`.`Title`),
+ `b0`.`Title` = CAST(`b0`.`Rating` AS char)
+""");
+ }
+
[ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore.")]
public override async Task Update_with_alias_uniquification_in_setter_subquery(bool async)
{
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreFixture.cs
index cf585adb5..db19e172a 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreFixture.cs
@@ -1,6 +1,8 @@
+using System;
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestModels.Northwind;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
@@ -10,4 +12,7 @@ public class NorthwindBulkUpdatesSingleStoreFixture : Northwin
{
protected override ITestStoreFactory TestStoreFactory
=> SingleStoreNorthwindTestStoreFactory.Instance;
+
+ protected override Type ContextType
+ => typeof(NorthwindSingleStoreContext);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs
index 76e63aa97..e4e652e5a 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs
@@ -16,7 +16,7 @@ public class NorthwindBulkUpdatesSingleStoreTest : NorthwindBulkUpdatesTestBase<
public NorthwindBulkUpdatesSingleStoreTest(
NorthwindBulkUpdatesSingleStoreFixture fixture,
ITestOutputHelper testOutputHelper)
- : base(fixture)
+ : base(fixture, testOutputHelper)
{
ClearLog();
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
@@ -363,7 +363,7 @@ public override async Task Delete_Where_using_navigation_2(bool async)
FROM `Order Details` AS `o`
INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
-WHERE `c`.`CustomerID` IS NOT NULL AND (`c`.`CustomerID` LIKE 'F%')
+WHERE `c`.`CustomerID` LIKE 'F%'
""");
}
@@ -509,7 +509,7 @@ public override async Task Delete_Where_optional_navigation_predicate(bool async
FROM `Order Details` AS `o`
INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
-WHERE `c`.`City` IS NOT NULL AND (`c`.`City` LIKE 'Se%')
+WHERE `c`.`City` LIKE 'Se%'
""");
}
@@ -644,8 +644,8 @@ public override async Task Update_Where_parameter_set_constant(bool async)
await base.Update_Where_parameter_set_constant(async);
AssertExecuteUpdateSql(
- """
-@__customer_0='ALFKI' (Size = 255)
+"""
+@__customer_0='ALFKI' (Size = 5) (DbType = StringFixedLength)
UPDATE `Customers` AS `c`
SET `c`.`ContactName` = 'Updated'
@@ -653,7 +653,7 @@ public override async Task Update_Where_parameter_set_constant(bool async)
""",
//
"""
-@__customer_0='ALFKI' (Size = 255)
+@__customer_0='ALFKI' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
@@ -679,7 +679,7 @@ public override async Task Update_Where_set_parameter(bool async)
AssertExecuteUpdateSql(
"""
-@__value_0='Abc' (Size = 4000)
+@__value_0='Abc' (Size = 30)
UPDATE `Customers` AS `c`
SET `c`.`ContactName` = @__value_0
@@ -693,7 +693,7 @@ public override async Task Update_Where_set_parameter_from_closure_array(bool as
AssertExecuteUpdateSql(
"""
-@__p_0='Abc' (Size = 4000)
+@__p_0='Abc' (Size = 30)
UPDATE `Customers` AS `c`
SET `c`.`ContactName` = @__p_0
@@ -719,7 +719,7 @@ public override async Task Update_Where_set_parameter_from_multilevel_property_a
AssertExecuteUpdateSql(
"""
-@__container_Containee_Property_0='Abc' (Size = 4000)
+@__container_Containee_Property_0='Abc' (Size = 30)
UPDATE `Customers` AS `c`
SET `c`.`ContactName` = @__container_Containee_Property_0
@@ -974,8 +974,12 @@ public override async Task Update_Where_Distinct_set_constant(bool async)
AssertExecuteUpdateSql(
"""
UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT DISTINCT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
SET `c`.`ContactName` = 'Updated'
-WHERE `c`.`CustomerID` LIKE 'F%'
""");
}
@@ -1038,7 +1042,7 @@ public override async Task Update_Where_set_property_plus_parameter(bool async)
AssertExecuteUpdateSql(
"""
-@__value_0='Abc' (Size = 4000)
+@__value_0='Abc' (Size = 30)
UPDATE `Customers` AS `c`
SET `c`.`ContactName` = CONCAT(COALESCE(`c`.`ContactName`, ''), @__value_0)
@@ -1102,7 +1106,7 @@ public override async Task Update_Where_multiple_set(bool async)
AssertExecuteUpdateSql(
"""
-@__value_0='Abc' (Size = 4000)
+@__value_0='Abc' (Size = 30)
UPDATE `Customers` AS `c`
SET `c`.`City` = 'Seattle',
@@ -1118,13 +1122,6 @@ public override async Task Update_with_invalid_lambda_in_set_property_throws(boo
AssertExecuteUpdateSql();
}
- public override async Task Update_multiple_entity_throws(bool async)
- {
- await base.Update_multiple_entity_throws(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_unmapped_property_throws(bool async)
{
await base.Update_unmapped_property_throws(async);
@@ -1307,7 +1304,7 @@ public override async Task Update_with_cross_join_left_join_set_constant(bool as
CROSS JOIN (
SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
FROM `Customers` AS `c0`
- WHERE `c0`.`City` IS NOT NULL AND (`c0`.`City` LIKE 'S%')
+ WHERE `c0`.`City` LIKE 'S%'
) AS `t`
LEFT JOIN (
SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
@@ -1329,7 +1326,7 @@ public override async Task Update_with_cross_join_cross_apply_set_constant(bool
CROSS JOIN (
SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
FROM `Customers` AS `c0`
- WHERE `c0`.`City` IS NOT NULL AND (`c0`.`City` LIKE 'S%')
+ WHERE `c0`.`City` LIKE 'S%'
) AS `t`
JOIN LATERAL (
SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
@@ -1351,7 +1348,7 @@ public override async Task Update_with_cross_join_outer_apply_set_constant(bool
CROSS JOIN (
SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
FROM `Customers` AS `c0`
- WHERE `c0`.`City` IS NOT NULL AND (`c0`.`City` LIKE 'S%')
+ WHERE `c0`.`City` LIKE 'S%'
) AS `t`
LEFT JOIN LATERAL (
SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
@@ -1444,6 +1441,49 @@ ORDER BY `o`.`OrderDate` DESC
""");
}
+ public override async Task Update_multiple_tables_throws(bool async)
+ {
+ await base.Update_multiple_tables_throws(async);
+
+ AssertSql(
+ """
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o`.`CustomerID` LIKE 'F%'
+ """);
+ }
+
+ public override async Task Update_with_two_inner_joins(bool async)
+ {
+ await base.Update_with_two_inner_joins(async);
+
+ AssertSql(
+ """
+ SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+ FROM `Order Details` AS `o`
+ INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ WHERE `p`.`Discontinued` AND (`o0`.`OrderDate` > '1990-01-01 00:00:00')
+ """,
+ //
+ """
+ UPDATE `Order Details` AS `o`
+ INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ SET `o`.`Quantity` = CAST(1 AS signed)
+ WHERE `p`.`Discontinued` AND (`o0`.`OrderDate` > '1990-01-01 00:00:00')
+ """,
+ //
+ """
+ SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+ FROM `Order Details` AS `o`
+ INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ WHERE `p`.`Discontinued` AND (`o0`.`OrderDate` > '1990-01-01 00:00:00')
+ """);
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
index a03b900ae..2a47a0e8b 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
@@ -5,6 +5,6 @@ public class TPCFiltersInheritanceBulkUpdatesSingleStoreFixture : TPCInheritance
protected override string StoreName
=> "TPCFiltersInheritanceBulkUpdatesTest";
- protected override bool EnableFilters
+ public override bool EnableFilters
=> true;
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreTest.cs
index 7e6f63d98..bfbb8980b 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSingleStoreTest.cs
@@ -2,14 +2,17 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
+using Xunit.Abstractions;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
public class TPCFiltersInheritanceBulkUpdatesSingleStoreTest : TPCFiltersInheritanceBulkUpdatesTestBase<
TPCFiltersInheritanceBulkUpdatesSingleStoreFixture>
{
- public TPCFiltersInheritanceBulkUpdatesSingleStoreTest(TPCFiltersInheritanceBulkUpdatesSingleStoreFixture fixture)
- : base(fixture)
+ public TPCFiltersInheritanceBulkUpdatesSingleStoreTest(
+ TPCFiltersInheritanceBulkUpdatesSingleStoreFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
ClearLog();
}
@@ -111,13 +114,6 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
- {
- await base.Update_where_hierarchy(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -125,18 +121,6 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
- {
- await base.Update_where_hierarchy_derived(async);
-
- AssertExecuteUpdateSql(
-"""
-UPDATE `Kiwi` AS `k`
-SET `k`.`Name` = 'Kiwi'
-WHERE (`k`.`CountryId` = 1) AND (`k`.`Name` = 'Great spotted kiwi')
-""");
- }
-
public override async Task Update_where_using_hierarchy(bool async)
{
await base.Update_where_using_hierarchy(async);
@@ -183,6 +167,112 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}
+ public override async Task Update_base_type(bool async)
+ {
+ await base.Update_base_type(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE (`t`.`CountryId` = 1) AND (`t`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+WHERE `k`.`CountryId` = 1
+""",
+ //
+ """
+UPDATE `Kiwi` AS `k`
+SET `k`.`Name` = 'SomeOtherKiwi'
+WHERE `k`.`CountryId` = 1
+""",
+ //
+ """
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+WHERE `k`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+WHERE `k`.`CountryId` = 1
+""",
+ //
+ """
+UPDATE `Kiwi` AS `k`
+SET `k`.`FoundOn` = 0
+WHERE `k`.`CountryId` = 1
+""",
+ //
+ """
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+WHERE `k`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+WHERE `k`.`CountryId` = 1
+""",
+ //
+ """
+UPDATE `Kiwi` AS `k`
+SET `k`.`FoundOn` = 0,
+ `k`.`Name` = 'Kiwi'
+WHERE `k`.`CountryId` = 1
+""",
+ //
+ """
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+WHERE `k`.`CountryId` = 1
+""");
+ }
+
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreFixture.cs
index 85caaf12a..6e9f9012c 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreFixture.cs
@@ -12,7 +12,7 @@ public class TPCInheritanceBulkUpdatesSingleStoreFixture : TPCInheritanceBulkUpd
protected override ITestStoreFactory TestStoreFactory
=> SingleStoreTestStoreFactory.Instance;
- protected override bool UseGeneratedKeys
+ public override bool UseGeneratedKeys
=> false;
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreTest.cs
index d6b1e7631..dc28a5585 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSingleStoreTest.cs
@@ -2,13 +2,16 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
+using Xunit.Abstractions;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
public class TPCInheritanceBulkUpdatesSingleStoreTest : TPCInheritanceBulkUpdatesTestBase
{
- public TPCInheritanceBulkUpdatesSingleStoreTest(TPCInheritanceBulkUpdatesSingleStoreFixture fixture)
- : base(fixture)
+ public TPCInheritanceBulkUpdatesSingleStoreTest(
+ TPCInheritanceBulkUpdatesSingleStoreFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
ClearLog();
}
@@ -106,13 +109,6 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
- {
- await base.Update_where_hierarchy(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -120,18 +116,6 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
- {
- await base.Update_where_hierarchy_derived(async);
-
- AssertExecuteUpdateSql(
-"""
-UPDATE `Kiwi` AS `k`
-SET `k`.`Name` = 'Kiwi'
-WHERE `k`.`Name` = 'Great spotted kiwi'
-""");
- }
-
public override async Task Update_where_using_hierarchy(bool async)
{
await base.Update_where_using_hierarchy(async);
@@ -178,6 +162,141 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}
+ public override async Task Update_base_type(bool async)
+ {
+ await base.Update_base_type(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+FROM `Kiwi` AS `k`
+""");
+ }
+
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+""",
+ //
+ """
+UPDATE `Kiwi` AS `k`
+SET `k`.`Name` = 'SomeOtherKiwi'
+""",
+ //
+ """
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+""",
+ //
+ """
+UPDATE `Kiwi` AS `k`
+SET `k`.`FoundOn` = 0
+""",
+ //
+ """
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+""");
+ }
+
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertSql(
+"""
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+""",
+ //
+ """
+UPDATE `Kiwi` AS `k`
+SET `k`.`FoundOn` = 0,
+ `k`.`Name` = 'Kiwi'
+""",
+ //
+ """
+SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`
+FROM `Kiwi` AS `k`
+""");
+ }
+
+ public override async Task Update_with_interface_in_property_expression(bool async)
+ {
+ await base.Update_with_interface_in_property_expression(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`SortIndex`, `c`.`CaffeineGrams`, `c`.`CokeCO2`, `c`.`SugarGrams`
+FROM `Coke` AS `c`
+""",
+ //
+ """
+UPDATE `Coke` AS `c`
+SET `c`.`SugarGrams` = 0
+""",
+ //
+ """
+SELECT `c`.`Id`, `c`.`SortIndex`, `c`.`CaffeineGrams`, `c`.`CokeCO2`, `c`.`SugarGrams`
+FROM `Coke` AS `c`
+""");
+ }
+
+ public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
+ {
+ await base.Update_with_interface_in_EF_Property_in_property_expression(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`SortIndex`, `c`.`CaffeineGrams`, `c`.`CokeCO2`, `c`.`SugarGrams`
+FROM `Coke` AS `c`
+""",
+ //
+ """
+UPDATE `Coke` AS `c`
+SET `c`.`SugarGrams` = 0
+""",
+ //
+ """
+SELECT `c`.`Id`, `c`.`SortIndex`, `c`.`CaffeineGrams`, `c`.`CokeCO2`, `c`.`SugarGrams`
+FROM `Coke` AS `c`
+""");
+ }
+
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
similarity index 86%
rename from test/EFCore.SingleStore.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSingleStoreFixture.cs
rename to test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
index 7a7e820c0..40543edda 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
@@ -3,12 +3,12 @@
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
-public class FiltersInheritanceBulkUpdatesSingleStoreFixture : InheritanceBulkUpdatesSingleStoreFixture
+public class TPHFiltersInheritanceBulkUpdatesSingleStoreFixture : TPHInheritanceBulkUpdatesSingleStoreFixture
{
protected override string StoreName
=> "FiltersInheritanceBulkUpdatesTest";
- protected override bool EnableFilters
+ public override bool EnableFilters
=> true;
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSingleStoreTest.cs
similarity index 64%
rename from test/EFCore.SingleStore.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSingleStoreTest.cs
rename to test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSingleStoreTest.cs
index 48f152cda..d583bf4a9 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSingleStoreTest.cs
@@ -1,15 +1,20 @@
+using System;
using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using SingleStoreConnector;
+using EntityFrameworkCore.SingleStore.Infrastructure;
+using EntityFrameworkCore.SingleStore.Tests;
+using EntityFrameworkCore.SingleStore.Tests.TestUtilities.Attributes;
using Xunit;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
-public class FiltersInheritanceBulkUpdatesSingleStoreTest : FiltersInheritanceBulkUpdatesTestBase<
- FiltersInheritanceBulkUpdatesSingleStoreFixture>
+public class TPHFiltersInheritanceBulkUpdatesSingleStoreTest : FiltersInheritanceBulkUpdatesTestBase<
+ TPHFiltersInheritanceBulkUpdatesSingleStoreFixture>
{
- public FiltersInheritanceBulkUpdatesSingleStoreTest(FiltersInheritanceBulkUpdatesSingleStoreFixture fixture)
+ public TPHFiltersInheritanceBulkUpdatesSingleStoreTest(TPHFiltersInheritanceBulkUpdatesSingleStoreFixture fixture)
: base(fixture)
{
ClearLog();
@@ -89,63 +94,73 @@ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
{
- // Not supported by MySQL:
- // Error Code: 1093. You can't specify target table 'c' for update in FROM clause
- await Assert.ThrowsAsync(
- () => base.Delete_GroupBy_Where_Select_First_3(async));
+ await base.Delete_GroupBy_Where_Select_First_3(async);
AssertSql(
"""
DELETE `a`
FROM `Animals` AS `a`
-WHERE (`a`.`CountryId` = 1) AND EXISTS (
- SELECT 1
- FROM `Animals` AS `a0`
- WHERE `a0`.`CountryId` = 1
- GROUP BY `a0`.`CountryId`
- HAVING (COUNT(*) < 3) AND ((
+WHERE (`a`.`CountryId` = 1) AND `a`.`Id` IN (
+ SELECT (
SELECT `a1`.`Id`
FROM `Animals` AS `a1`
WHERE (`a1`.`CountryId` = 1) AND (`a0`.`CountryId` = `a1`.`CountryId`)
- LIMIT 1) = `a`.`Id`))
-"""
-);
+ LIMIT 1)
+ FROM `Animals` AS `a0`
+ WHERE `a0`.`CountryId` = 1
+ GROUP BY `a0`.`CountryId`
+ HAVING COUNT(*) < 3
+)
+""");
}
- public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ public override async Task Update_where_hierarchy_subquery(bool async)
{
- await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+ await base.Update_where_hierarchy_subquery(async);
- AssertSql();
+ AssertExecuteUpdateSql();
}
- public override async Task Delete_where_hierarchy_subquery(bool async)
+ public override async Task Update_where_using_hierarchy(bool async)
{
- await base.Delete_where_hierarchy_subquery(async);
+ await base.Update_where_using_hierarchy(async);
- AssertSql(
+ AssertExecuteUpdateSql(
"""
-@__p_1='3'
-@__p_0='0'
-
-DELETE `a`
-FROM `Animals` AS `a`
-WHERE EXISTS (
- SELECT 1
- FROM (
- SELECT `a0`.`Id`, `a0`.`CountryId`, `a0`.`Discriminator`, `a0`.`Name`, `a0`.`Species`, `a0`.`EagleId`, `a0`.`IsFlightless`, `a0`.`Group`, `a0`.`FoundOn`
- FROM `Animals` AS `a0`
- WHERE (`a0`.`CountryId` = 1) AND (`a0`.`Name` = 'Great spotted kiwi')
- ORDER BY `a0`.`Name`
- LIMIT @__p_1 OFFSET @__p_0
- ) AS `t`
- WHERE `t`.`Id` = `a`.`Id`)
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE ((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`CountryId` > 0)) > 0
""");
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_where_using_hierarchy_derived(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+ """
+ UPDATE `Countries` AS `c`
+ SET `c`.`Name` = 'Monovia'
+ WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE (((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`Discriminator` = 'Kiwi')) AND (`a`.`CountryId` > 0)) > 0
+ """);
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type(bool async)
+ {
+ await base.Update_base_type(async);
AssertExecuteUpdateSql(
"""
@@ -155,60 +170,68 @@ public override async Task Update_where_hierarchy(bool async)
""");
}
- public override async Task Update_where_hierarchy_subquery(bool async)
+ public override async Task Update_base_type_with_OfType(bool async)
{
- await base.Update_where_hierarchy_subquery(async);
+ await base.Update_base_type_with_OfType(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+ """
+ UPDATE `Animals` AS `a`
+ SET `a`.`Name` = 'NewBird'
+ WHERE (`a`.`CountryId` = 1) AND (`a`.`Discriminator` = 'Kiwi')
+ """);
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE `Animals` AS `a`
-SET `a`.`Name` = 'Kiwi'
-WHERE ((`a`.`Discriminator` = 'Kiwi') AND (`a`.`CountryId` = 1)) AND (`a`.`Name` = 'Great spotted kiwi')
+SET `a`.`Name` = 'SomeOtherKiwi'
+WHERE (`a`.`Discriminator` = 'Kiwi') AND (`a`.`CountryId` = 1)
""");
}
- public override async Task Update_where_using_hierarchy(bool async)
+ public override async Task Update_derived_property_on_derived_type(bool async)
{
- await base.Update_where_using_hierarchy(async);
+ await base.Update_derived_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
-UPDATE `Countries` AS `c`
-SET `c`.`Name` = 'Monovia'
-WHERE (
- SELECT COUNT(*)
- FROM `Animals` AS `a`
- WHERE ((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`CountryId` > 0)) > 0
+UPDATE `Animals` AS `a`
+SET `a`.`FoundOn` = 0
+WHERE (`a`.`Discriminator` = 'Kiwi') AND (`a`.`CountryId` = 1)
""");
}
- public override async Task Update_where_using_hierarchy_derived(bool async)
+ public override async Task Update_base_and_derived_types(bool async)
{
- await base.Update_where_using_hierarchy_derived(async);
+ await base.Update_base_and_derived_types(async);
AssertExecuteUpdateSql(
"""
-UPDATE `Countries` AS `c`
-SET `c`.`Name` = 'Monovia'
-WHERE (
- SELECT COUNT(*)
- FROM `Animals` AS `a`
- WHERE (((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`Discriminator` = 'Kiwi')) AND (`a`.`CountryId` > 0)) > 0
+UPDATE `Animals` AS `a`
+SET `a`.`FoundOn` = 0,
+ `a`.`Name` = 'Kiwi'
+WHERE (`a`.`Discriminator` = 'Kiwi') AND (`a`.`CountryId` = 1)
""");
}
- public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.LimitWithinInAllAnySomeSubquery))]
+ public override async Task Delete_where_hierarchy_subquery(bool async)
{
- await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+ await base.Delete_where_hierarchy_subquery(async);
- AssertExecuteUpdateSql();
+ AssertSql();
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
}
protected override void ClearLog()
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSingleStoreFixture.cs
similarity index 92%
rename from test/EFCore.SingleStore.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSingleStoreFixture.cs
rename to test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSingleStoreFixture.cs
index ebd6220a2..4f4b05c75 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSingleStoreFixture.cs
@@ -6,7 +6,7 @@
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
-public class InheritanceBulkUpdatesSingleStoreFixture : InheritanceBulkUpdatesRelationalFixture
+public class TPHInheritanceBulkUpdatesSingleStoreFixture : TPHInheritanceBulkUpdatesFixture
{
protected override ITestStoreFactory TestStoreFactory
=> SingleStoreTestStoreFactory.Instance;
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSingleStoreTest.cs
similarity index 68%
rename from test/EFCore.SingleStore.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSingleStoreTest.cs
rename to test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSingleStoreTest.cs
index 69c38d2d9..6f1a7005b 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSingleStoreTest.cs
@@ -2,13 +2,15 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using SingleStoreConnector;
+using EntityFrameworkCore.SingleStore.Infrastructure;
+using EntityFrameworkCore.SingleStore.Tests.TestUtilities.Attributes;
using Xunit;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
-public class InheritanceBulkUpdatesSingleStoreTest : InheritanceBulkUpdatesTestBase
+public class TPHInheritanceBulkUpdatesSingleStoreTest : TPHInheritanceBulkUpdatesTestBase
{
- public InheritanceBulkUpdatesSingleStoreTest(InheritanceBulkUpdatesSingleStoreFixture fixture)
+ public TPHInheritanceBulkUpdatesSingleStoreTest(TPHInheritanceBulkUpdatesSingleStoreFixture fixture)
: base(fixture)
{
ClearLog();
@@ -93,15 +95,16 @@ await Assert.ThrowsAsync(
"""
DELETE `a`
FROM `Animals` AS `a`
-WHERE EXISTS (
- SELECT 1
- FROM `Animals` AS `a0`
- GROUP BY `a0`.`CountryId`
- HAVING (COUNT(*) < 3) AND ((
+WHERE `a`.`Id` IN (
+SELECT (
SELECT `a1`.`Id`
FROM `Animals` AS `a1`
WHERE `a0`.`CountryId` = `a1`.`CountryId`
- LIMIT 1) = `a`.`Id`))
+ LIMIT 1)
+ FROM `Animals` AS `a0`
+ GROUP BY `a0`.`CountryId`
+ HAVING COUNT(*) < 3
+)
""");
}
@@ -112,6 +115,7 @@ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool
AssertSql();
}
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.LimitWithinInAllAnySomeSubquery))]
public override async Task Delete_where_hierarchy_subquery(bool async)
{
await base.Delete_where_hierarchy_subquery(async);
@@ -136,18 +140,6 @@ LIMIT @__p_1 OFFSET @__p_0
""");
}
- public override async Task Update_where_hierarchy(bool async)
- {
- await base.Update_where_hierarchy(async);
-
- AssertExecuteUpdateSql(
-"""
-UPDATE `Animals` AS `a`
-SET `a`.`Name` = 'Animal'
-WHERE `a`.`Name` = 'Great spotted kiwi'
-""");
- }
-
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -155,18 +147,6 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
- {
- await base.Update_where_hierarchy_derived(async);
-
- AssertExecuteUpdateSql(
-"""
-UPDATE `Animals` AS `a`
-SET `a`.`Name` = 'Kiwi'
-WHERE (`a`.`Discriminator` = 'Kiwi') AND (`a`.`Name` = 'Great spotted kiwi')
-""");
- }
-
public override async Task Update_where_using_hierarchy(bool async)
{
await base.Update_where_using_hierarchy(async);
@@ -204,6 +184,91 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}
+ public override async Task Update_base_type(bool async)
+ {
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'Animal'
+WHERE `a`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'NewBird'
+WHERE `a`.`Discriminator` = 'Kiwi'
+""");
+ }
+
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'SomeOtherKiwi'
+WHERE `a`.`Discriminator` = 'Kiwi'
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`FoundOn` = 0
+WHERE `a`.`Discriminator` = 'Kiwi'
+""");
+ }
+
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`FoundOn` = 0,
+ `a`.`Name` = 'Kiwi'
+WHERE `a`.`Discriminator` = 'Kiwi'
+""");
+ }
+
+ public override async Task Update_with_interface_in_property_expression(bool async)
+ {
+ await base.Update_with_interface_in_property_expression(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Drinks` AS `d`
+SET `d`.`SugarGrams` = 0
+WHERE `d`.`Discriminator` = 1
+""");
+ }
+
+ public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
+ {
+ await base.Update_with_interface_in_EF_Property_in_property_expression(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Drinks` AS `d`
+SET `d`.`SugarGrams` = 0
+WHERE `d`.`Discriminator` = 1
+""");
+ }
+
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
index 2dc8607a5..5cd71adc9 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreFixture.cs
@@ -8,7 +8,7 @@ public class TPTFiltersInheritanceBulkUpdatesSingleStoreFixture : TPTInheritance
protected override string StoreName
=> "TPTFiltersInheritanceBulkUpdatesTest";
- protected override bool EnableFilters
+ public override bool EnableFilters
=> true;
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreTest.cs
index a27134852..2a13f3b3b 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSingleStoreTest.cs
@@ -3,14 +3,17 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
+using Xunit.Abstractions;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
public class TPTFiltersInheritanceBulkUpdatesSingleStoreTest : TPTFiltersInheritanceBulkUpdatesTestBase<
TPTFiltersInheritanceBulkUpdatesSingleStoreFixture>
{
- public TPTFiltersInheritanceBulkUpdatesSingleStoreTest(TPTFiltersInheritanceBulkUpdatesSingleStoreFixture fixture)
- : base(fixture)
+ public TPTFiltersInheritanceBulkUpdatesSingleStoreTest(
+ TPTFiltersInheritanceBulkUpdatesSingleStoreFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
ClearLog();
}
@@ -111,20 +114,6 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
- {
- // We're skipping this test when we're running tests on Managed Service due to the specifics of
- // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
- if (AppConfig.ManagedService)
- {
- return;
- }
-
- await base.Update_where_hierarchy(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -132,13 +121,6 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
- {
- await base.Update_where_hierarchy_derived(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_where_using_hierarchy(bool async)
{
// We're skipping this test when we're running tests on Managed Service due to the specifics of
@@ -196,6 +178,88 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql(
+ """
+ UPDATE `Animals` AS `a`
+ SET `a`.`Name` = 'Animal'
+ WHERE (`a`.`CountryId` = 1) AND (`a`.`Name` = 'Great spotted kiwi')
+ """);
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+ """
+ UPDATE `Animals` AS `a`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ SET `a`.`Name` = 'NewBird'
+ WHERE (`a`.`CountryId` = 1) AND `k`.`Id` IS NOT NULL
+ """);
+ }
+
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+ """
+ UPDATE `Animals` AS `a`
+ INNER JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ INNER JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ SET `a`.`Name` = 'SomeOtherKiwi'
+ WHERE `a`.`CountryId` = 1
+ """);
+ }
+
+ [ConditionalTheory(Skip = "Operation 'Update/Delete right table of a join' is not allowed.")]
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+ """
+ UPDATE `Animals` AS `a`
+ INNER JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ INNER JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ SET `k`.`FoundOn` = 0
+ WHERE `a`.`CountryId` = 1
+ """);
+ }
+
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSingleStoreTest.cs
index ad144c180..117f81abd 100644
--- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSingleStoreTest.cs
@@ -3,13 +3,16 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
+using Xunit.Abstractions;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.BulkUpdates;
public class TPTInheritanceBulkUpdatesSingleStoreTest : TPTInheritanceBulkUpdatesTestBase
{
- public TPTInheritanceBulkUpdatesSingleStoreTest(TPTInheritanceBulkUpdatesSingleStoreFixture fixture)
- : base(fixture)
+ public TPTInheritanceBulkUpdatesSingleStoreTest(
+ TPTInheritanceBulkUpdatesSingleStoreFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
ClearLog();
}
@@ -77,20 +80,6 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
- {
- // We're skipping this test when we're running tests on Managed Service due to the specifics of
- // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
- if (AppConfig.ManagedService)
- {
- return;
- }
-
- await base.Update_where_hierarchy(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -98,13 +87,6 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
- {
- await base.Update_where_hierarchy_derived(async);
-
- AssertExecuteUpdateSql();
- }
-
public override async Task Update_where_using_hierarchy(bool async)
{
// We're skipping this test when we're running tests on Managed Service due to the specifics of
@@ -162,6 +144,112 @@ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool
AssertExecuteUpdateSql();
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'Animal'
+WHERE `a`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+SET `a`.`Name` = 'NewBird'
+WHERE `k`.`Id` IS NOT NULL
+""");
+ }
+
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+INNER JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+INNER JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+SET `a`.`Name` = 'SomeOtherKiwi'
+""");
+ }
+
+ [ConditionalTheory(Skip = "Operation 'Update/Delete right table of a join' is not allowed.")]
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+INNER JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+INNER JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+SET `k`.`FoundOn` = 0
+""");
+ }
+
+ [ConditionalTheory(Skip = "Operation 'Update/Delete right table of a join' is not allowed.")]
+ public override async Task Update_with_interface_in_property_expression(bool async)
+ {
+ await base.Update_with_interface_in_property_expression(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Drinks` AS `d`
+INNER JOIN `Coke` AS `c` ON `d`.`Id` = `c`.`Id`
+SET `c`.`SugarGrams` = 0
+""");
+ }
+
+ [ConditionalTheory(Skip = "Operation 'Update/Delete right table of a join' is not allowed.")]
+ public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
+ {
+ await base.Update_with_interface_in_EF_Property_in_property_expression(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Drinks` AS `d`
+INNER JOIN `Coke` AS `c` ON `d`.`Id` = `c`.`Id`
+SET `c`.`SugarGrams` = 0
+""");
+ }
+
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
diff --git a/test/EFCore.SingleStore.FunctionalTests/ComplexTypesTrackingSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ComplexTypesTrackingSingleStoreTest.cs
new file mode 100644
index 000000000..e044edd1f
--- /dev/null
+++ b/test/EFCore.SingleStore.FunctionalTests/ComplexTypesTrackingSingleStoreTest.cs
@@ -0,0 +1,248 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using Xunit.Abstractions;
+
+namespace EntityFrameworkCore.SingleStore.FunctionalTests;
+
+public class ComplexTypesTrackingSingleStoreTest : ComplexTypesTrackingTestBase
+{
+ public ComplexTypesTrackingSingleStoreTest(SingleStoreFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ fixture.TestSqlLoggerFactory.Clear();
+ fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
+ => facade.UseTransaction(transaction.GetDbTransaction());
+
+ public class SingleStoreFixture : FixtureBase
+ {
+ protected override ITestStoreFactory TestStoreFactory
+ => SingleStoreTestStoreFactory.Instance;
+
+ public TestSqlLoggerFactory TestSqlLoggerFactory
+ => (TestSqlLoggerFactory)ListLoggerFactory;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+ //
+ // // TODO: Allow binding of complex properties to constructors
+ // // modelBuilder.Entity(
+ // // b =>
+ // // {
+ // // b.ComplexProperty(
+ // // e => e.LunchtimeActivity, b =>
+ // // {
+ // // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // // b.Ignore(e => e!.Notes);
+ // // });
+ // // b.ComplexProperty(
+ // // e => e.EveningActivity, b =>
+ // // {
+ // // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // // b.Ignore(e => e.Notes);
+ // // });
+ // // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // // });
+ //
+ // modelBuilder.Entity(
+ // b =>
+ // {
+ // b.ComplexProperty(
+ // e => e.LunchtimeActivity, b =>
+ // {
+ // b.ComplexProperty(e => e!.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e!.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e!.Notes);
+ // });
+ // b.ComplexProperty(
+ // e => e.EveningActivity, b =>
+ // {
+ // b.ComplexProperty(e => e.Champions, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.RunnersUp, b => b.Ignore(e => e.Members));
+ // b.Ignore(e => e.Notes);
+ // });
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // b.ComplexProperty(e => e.FeaturedTeam, b => b.Ignore(e => e.Members));
+ // });
+
+ // // TODO: We currently don't support primitive collections yet.
+ // var entityTypes = modelBuilder.Model.GetEntityTypes().ToArray();
+ // var unprocessedComplexProperties = new Queue(entityTypes.SelectMany(e => e.GetComplexProperties()));
+ // var processedComplexProperties = new HashSet();
+ //
+ // while (unprocessedComplexProperties.TryDequeue(out var complexProperty))
+ // {
+ // processedComplexProperties.Add(complexProperty);
+ //
+ // Debug.WriteLine(complexProperty.ClrType.Name);
+ //
+ // var unmappedProperties = complexProperty.ComplexType.ClrType
+ // .GetProperties()
+ // .Except(
+ // complexProperty.ComplexType
+ // .GetProperties()
+ // .Where(p => p.PropertyInfo is not null)
+ // .Select(p => p.PropertyInfo));
+ //
+ // foreach (var property in unmappedProperties)
+ // {
+ //
+ // // if (propertyInfo.IsGenericType &&
+ // // propertyInfo.GetGenericTypeDefinition() == typeof(List<>))
+ // // {
+ // // Debug.WriteLine($"{propertyInfo.Name} {complexProperty.ClrType.Name}.{property.Name}");
+ // // }
+ // }
+ //
+ // foreach (var innerComplexProperty in complexProperty.ComplexType.GetComplexProperties())
+ // {
+ // if (!processedComplexProperties.Contains(innerComplexProperty) &&
+ // !unprocessedComplexProperties.Contains(innerComplexProperty))
+ // {
+ // unprocessedComplexProperties.Enqueue(innerComplexProperty);
+ // }
+ // }
+ // }
+ }
+ }
+}
diff --git a/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs
index afa471d82..3ecc2551b 100644
--- a/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using SingleStoreConnector;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using EntityFrameworkCore.SingleStore.Storage.Internal;
using EntityFrameworkCore.SingleStore.Tests;
using Xunit;
@@ -68,6 +72,7 @@ public virtual void ConnectionAttributes()
{
dbConnAttrs.Add(new Tuple(reader.GetString(0), reader.GetString(1)));
}
+
Tuple expectedPair = new Tuple("my_key_1", "my_val_1");
Assert.Contains(expectedPair, dbConnAttrs);
expectedPair = new Tuple("my_key_2", "my_val_2");
@@ -75,28 +80,126 @@ public virtual void ConnectionAttributes()
}
}
+ [Fact]
+ public void UseSingleStore_IncludesConnectorAttributes_InConnectionString()
+ {
+ using var _ = ((SingleStoreTestStore)SingleStoreNorthwindTestStoreFactory.Instance
+ .GetOrCreate("ConnectionAttributesTest"))
+ .Initialize(null, (Func)null);
+
+ var cs = SingleStoreTestStore.CreateConnectionString("ConnectionAttributesTest");
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseSingleStore(cs, b => b.ApplyConfiguration());
+
+ using var context = new GeneralOptionsContext(optionsBuilder.Options);
+
+ var conn = (SingleStoreConnection)context.Database.GetDbConnection();
+ var csb = new SingleStoreConnectionStringBuilder(conn.ConnectionString);
+
+ var parts = csb.ConnectionAttributes
+ .TrimEnd(',')
+ .Split(',', StringSplitOptions.RemoveEmptyEntries)
+ .Select(p => p.Trim())
+ .ToArray();
+
+ var programVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
+ Assert.Contains(parts, p => p.StartsWith("_connector_name:SingleStore Entity Framework Core provider"));
+ Assert.Contains(parts, p => p.StartsWith($"_connector_version:{programVersion}"));
+ }
+
+ [Fact]
+ public void Can_create_admin_connection_with_data_source()
+ {
+ using var _ = ((SingleStoreTestStore)SingleStoreNorthwindTestStoreFactory.Instance
+ .GetOrCreate("ConnectionTest"))
+ .Initialize(null, (Func)null);
+
+ using var dataSource = new SingleStoreDataSourceBuilder(SingleStoreTestStore.CreateConnectionString("ConnectionTest")).Build();
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseSingleStore(dataSource, b => b.ApplyConfiguration());
+ using var context = new GeneralOptionsContext(optionsBuilder.Options);
+
+ var relationalConnection = context.GetService();
+ using var masterConnection = relationalConnection.CreateMasterConnection();
+
+ Assert.Equal(string.Empty, new SingleStoreConnectionStringBuilder(masterConnection.ConnectionString).Database);
+
+ masterConnection.Open();
+ }
+
+ [Fact]
+ public void Can_create_admin_connection_with_connection_string()
+ {
+ using var _ = ((SingleStoreTestStore)SingleStoreNorthwindTestStoreFactory.Instance
+ .GetOrCreate("ConnectionTest"))
+ .Initialize(null, (Func)null);
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseSingleStore(SingleStoreTestStore.CreateConnectionString("ConnectionTest"),
+ b => b.ApplyConfiguration());
+ using var context = new GeneralOptionsContext(optionsBuilder.Options);
+
+ var relationalConnection = context.GetService();
+ using var masterConnection = relationalConnection.CreateMasterConnection();
+
+ Assert.Equal(string.Empty, new SingleStoreConnectionStringBuilder(masterConnection.ConnectionString).Database);
+
+ masterConnection.Open();
+ }
+
+ [Fact]
+ public void Can_create_admin_connection_with_connection()
+ {
+ using var _ = ((SingleStoreTestStore)SingleStoreNorthwindTestStoreFactory.Instance
+ .GetOrCreate("ConnectionTestWithConnection"))
+ .Initialize(null, (Func)null);
+
+ using var connection = new SingleStoreConnection(SingleStoreTestStore.CreateConnectionString("ConnectionTestWithConnection"));
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseSingleStore(connection, b => b.ApplyConfiguration());
+ using var context = new GeneralOptionsContext(optionsBuilder.Options);
+
+ var relationalConnection = context.GetService();
+ using var masterConnection = relationalConnection.CreateMasterConnection();
+
+ Assert.Equal(string.Empty, new SingleStoreConnectionStringBuilder(masterConnection.ConnectionString).Database);
+
+ masterConnection.Open();
+ }
+
private readonly IServiceProvider _serviceProvider = new ServiceCollection()
.AddEntityFrameworkSingleStore()
.BuildServiceProvider();
- protected ConnectionMysqlContext CreateContext(string connectionString)
- => new ConnectionMysqlContext(_serviceProvider, connectionString);
- }
+ protected ConnectionSingleStoreContext CreateContext(string connectionString)
+ => new ConnectionSingleStoreContext(_serviceProvider, connectionString);
- public class ConnectionMysqlContext : DbContext
- {
- private readonly IServiceProvider _serviceProvider;
- private readonly string _connectionString;
- public ConnectionMysqlContext(IServiceProvider serviceProvider, string connectionString)
+ public class ConnectionSingleStoreContext : DbContext
{
- _serviceProvider = serviceProvider;
- _connectionString = connectionString;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly string _connectionString;
+
+ public ConnectionSingleStoreContext(IServiceProvider serviceProvider, string connectionString)
+ {
+ _serviceProvider = serviceProvider;
+ _connectionString = connectionString;
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder
+ .UseSingleStore(_connectionString)
+ .UseInternalServiceProvider(_serviceProvider);
}
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- => optionsBuilder
- .UseSingleStore(_connectionString)
- .UseInternalServiceProvider(_serviceProvider);
+ public class GeneralOptionsContext : DbContext
+ {
+ public GeneralOptionsContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+ }
}
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/ConvertToProviderTypesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ConvertToProviderTypesSingleStoreTest.cs
index 07040f36f..ba58d25d1 100644
--- a/test/EFCore.SingleStore.FunctionalTests/ConvertToProviderTypesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/ConvertToProviderTypesSingleStoreTest.cs
@@ -23,31 +23,31 @@ public ConvertToProviderTypesSingleStoreTest(ConvertToProviderTypesSingleStoreFi
//fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_read_back_mapped_enum_from_collection_first_or_default()
{
base.Can_read_back_mapped_enum_from_collection_first_or_default();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_binary_key()
{
base.Can_insert_and_read_back_with_binary_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_null_binary_foreign_key()
{
base.Can_insert_and_read_back_with_null_binary_foreign_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_null_string_foreign_key()
{
base.Can_insert_and_read_back_with_null_string_foreign_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_string_key()
{
base.Can_insert_and_read_back_with_string_key();
@@ -478,7 +478,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- //FOREIGN KEY feature isn't supported by SingleStore
+ //FOREIGN KEY feature isn't supported by SingleStore Distributed
modelBuilder.Ignore();
modelBuilder.Ignore();
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/CustomConvertersSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/CustomConvertersSingleStoreTest.cs
index af143ac0a..500a39d65 100644
--- a/test/EFCore.SingleStore.FunctionalTests/CustomConvertersSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/CustomConvertersSingleStoreTest.cs
@@ -4,6 +4,7 @@
using System.Linq.Expressions;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.TestUtilities;
@@ -36,37 +37,37 @@ public override void Optional_datetime_reading_null_from_database()
// Recheck/remove after `https://github.com/dotnet/efcore/issues/26068` has been fixed upstream.
#region https://github.com/dotnet/efcore/issues/26068
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_binary_key()
{
base.Can_insert_and_read_back_with_binary_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_null_binary_foreign_key()
{
base.Can_insert_and_read_back_with_null_binary_foreign_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_null_string_foreign_key()
{
base.Can_insert_and_read_back_with_null_string_foreign_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_string_key()
{
base.Can_insert_and_read_back_with_string_key();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_read_back_mapped_enum_from_collection_first_or_default()
{
base.Can_read_back_mapped_enum_from_collection_first_or_default();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override void Can_insert_and_read_back_with_case_insensitive_string_key()
{
base.Can_insert_and_read_back_with_case_insensitive_string_key();
@@ -478,7 +479,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- //FOREIGN KEY feature isn't supported by SingleStore
+ //FOREIGN KEY feature isn't supported by SingleStore Distributed
modelBuilder.Ignore();
modelBuilder.Ignore();
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/DataAnnotationSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/DataAnnotationSingleStoreTest.cs
index 9e08fe401..7479bbe84 100644
--- a/test/EFCore.SingleStore.FunctionalTests/DataAnnotationSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/DataAnnotationSingleStoreTest.cs
@@ -91,7 +91,7 @@ public override IModel Timestamp_takes_precedence_over_MaxLength()
{
var model = base.Timestamp_takes_precedence_over_MaxLength();
- var property = GetProperty(model, "MaxTimestamp");
+ var property = GetProperty(model, "MaxTimestamp");
var storeTypeNameBase = property.GetRelationalTypeMapping().StoreTypeNameBase;
diff --git a/test/EFCore.SingleStore.FunctionalTests/DatabindingSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/DatabindingSingleStoreTest.cs
index e8374dd2d..36d6f0006 100644
--- a/test/EFCore.SingleStore.FunctionalTests/DatabindingSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/DatabindingSingleStoreTest.cs
@@ -3,16 +3,11 @@
namespace EntityFrameworkCore.SingleStore.FunctionalTests
{
- public class DatabindingSingleStoreTest : DatabindingTestBase
+ public class DatabindingSingleStoreTest : DataBindingTestBase
{
public DatabindingSingleStoreTest(F1SingleStoreFixture fixture)
: base(fixture)
{
}
-
- public override void DbSet_Local_calls_DetectChanges()
- {
- base.DbSet_Local_calls_DetectChanges();
- }
}
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj b/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj
index 7916c4d9b..74b52e0e5 100644
--- a/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj
+++ b/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj
@@ -10,6 +10,7 @@
$(MSBuildWarningsAsMessages);$(NoWarn)
+ $(DefineConstants);FIXED_TEST_ORDER
@@ -17,6 +18,10 @@
+
+
+
+
diff --git a/test/EFCore.SingleStore.FunctionalTests/ExistingConnectionSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ExistingConnectionSingleStoreTest.cs
index a3b3db720..ffec5067f 100644
--- a/test/EFCore.SingleStore.FunctionalTests/ExistingConnectionSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/ExistingConnectionSingleStoreTest.cs
@@ -2,6 +2,8 @@
using System.Data;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using SingleStoreConnector;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
@@ -119,14 +121,19 @@ private static async Task Opened_connection_missing_AllowUserVariables_true_in_o
{
await connection.OpenAsync();
+ var options = new DbContextOptionsBuilder()
+ .UseSingleStore(connection)
+ .Options;
+
+ await using var context = new NorthwindContext(options);
+
Assert.Equal(
Assert.Throws(
() =>
{
- new DbContextOptionsBuilder()
- .UseSingleStore(connection);
+ context.GetService();
}).Message,
- @"The connection string of a connection used by EntityFrameworkCore.SingleStore must contain ""AllowUserVariables=true;UseAffectedRows=false"".");
+ @"The connection string of a connection used by EntityFrameworkCore.SingleStore must contain ""AllowUserVariables=True;UseAffectedRows=False"".");
}
}
}
@@ -175,14 +182,19 @@ private static async Task Opened_connection_missing_UseAffectedRows_false_in_ori
{
await connection.OpenAsync();
+ var options = new DbContextOptionsBuilder()
+ .UseSingleStore(connection)
+ .Options;
+
+ await using var context = new NorthwindContext(options);
+
Assert.Equal(
Assert.Throws(
() =>
{
- new DbContextOptionsBuilder()
- .UseSingleStore(connection);
+ context.GetService();
}).Message,
- @"The connection string of a connection used by EntityFrameworkCore.SingleStore must contain ""AllowUserVariables=true;UseAffectedRows=false"".");
+ @"The connection string of a connection used by EntityFrameworkCore.SingleStore must contain ""AllowUserVariables=True;UseAffectedRows=False"".");
}
}
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/FullMigrationsSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/FullMigrationsSingleStoreTest.cs
index a6b0896e1..a82d0d344 100644
--- a/test/EFCore.SingleStore.FunctionalTests/FullMigrationsSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/FullMigrationsSingleStoreTest.cs
@@ -164,7 +164,7 @@ INTO PRIMARY_KEY_COLUMN_NAME
ignoreLineEndingDifferences: true);
}
- [ConditionalFact(Skip = "Feature 'DROP PRIMARY KEY' is not supported by SingleStore")]
+ [ConditionalFact(Skip = "Feature 'DROP PRIMARY KEY' is not supported by SingleStore Distributed")]
public virtual void Can_generate_idempotent_up_scripts_with_primary_key_related_stored_procedures()
{
using var db = Fixture.CreateContext();
@@ -371,7 +371,7 @@ IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000
ignoreLineEndingDifferences: true);
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public virtual void Drop_primary_key_with_recreating_foreign_keys()
{
using var db = Fixture.CreateContext();
diff --git a/test/EFCore.SingleStore.FunctionalTests/GraphUpdatesSingleStoreTestBase.cs b/test/EFCore.SingleStore.FunctionalTests/GraphUpdatesSingleStoreTestBase.cs
index 461da0199..93731f89f 100644
--- a/test/EFCore.SingleStore.FunctionalTests/GraphUpdatesSingleStoreTestBase.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/GraphUpdatesSingleStoreTestBase.cs
@@ -47,6 +47,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.HasOne(e => e.UserState).WithMany(e => e.Users).HasForeignKey(e => e.IdUserState);
});
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.AccessStateWithSentinelId).ValueGeneratedNever();
+ b.HasData(new AccessStateWithSentinel { AccessStateWithSentinelId = 1 });
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.IdUserState).HasDefaultValue(1).Metadata.Sentinel = 667;
+ b.HasOne(e => e.UserState).WithMany(e => e.Users).HasForeignKey(e => e.IdUserState);
+ });
+
modelBuilder.Entity().Property("CategoryId").HasDefaultValue(1);
modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/LazyLoadProxySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/LazyLoadProxySingleStoreTest.cs
index f66052c12..8dc35cec2 100644
--- a/test/EFCore.SingleStore.FunctionalTests/LazyLoadProxySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/LazyLoadProxySingleStoreTest.cs
@@ -16,19 +16,21 @@ public LazyLoadProxySingleStoreTest(LoadSingleStoreFixture fixture)
public override void Top_level_projection_track_entities_before_passing_to_client_method()
{
base.Top_level_projection_track_entities_before_passing_to_client_method();
- RecordLog();
Assert.Equal(
- @"SELECT `p`.`Id`, `p`.`AlternateId`, `p`.`Discriminator`
+"""
+SELECT `p`.`Id`, `p`.`AlternateId`, `p`.`Discriminator`, `p`.`Culture_Rating`, `p`.`Culture_Species`, `p`.`Culture_Subspecies`, `p`.`Culture_Validation`, `p`.`Culture_License_Charge`, `p`.`Culture_License_Title`, `p`.`Culture_License_Tag_Text`, `p`.`Culture_License_Tog_Text`, `p`.`Culture_Manufacturer_Name`, `p`.`Culture_Manufacturer_Rating`, `p`.`Culture_Manufacturer_Tag_Text`, `p`.`Culture_Manufacturer_Tog_Text`, `p`.`Milk_Rating`, `p`.`Milk_Species`, `p`.`Milk_Subspecies`, `p`.`Milk_Validation`, `p`.`Milk_License_Charge`, `p`.`Milk_License_Title`, `p`.`Milk_License_Tag_Text`, `p`.`Milk_License_Tog_Text`, `p`.`Milk_Manufacturer_Name`, `p`.`Milk_Manufacturer_Rating`, `p`.`Milk_Manufacturer_Tag_Text`, `p`.`Milk_Manufacturer_Tog_Text`
FROM `Parent` AS `p`
ORDER BY `p`.`Id`
LIMIT 1
@__p_0='707' (Nullable = true)
-SELECT `s`.`Id`, `s`.`ParentId`
+SELECT `s`.`Id`, `s`.`ParentId`, `s`.`Culture_Rating`, `s`.`Culture_Species`, `s`.`Culture_Subspecies`, `s`.`Culture_Validation`, `s`.`Culture_License_Charge`, `s`.`Culture_License_Title`, `s`.`Culture_License_Tag_Text`, `s`.`Culture_License_Tog_Text`, `s`.`Culture_Manufacturer_Name`, `s`.`Culture_Manufacturer_Rating`, `s`.`Culture_Manufacturer_Tag_Text`, `s`.`Culture_Manufacturer_Tog_Text`, `s`.`Milk_Rating`, `s`.`Milk_Species`, `s`.`Milk_Subspecies`, `s`.`Milk_Validation`, `s`.`Milk_License_Charge`, `s`.`Milk_License_Title`, `s`.`Milk_License_Tag_Text`, `s`.`Milk_License_Tog_Text`, `s`.`Milk_Manufacturer_Name`, `s`.`Milk_Manufacturer_Rating`, `s`.`Milk_Manufacturer_Tag_Text`, `s`.`Milk_Manufacturer_Tog_Text`
FROM `Single` AS `s`
-WHERE `s`.`ParentId` = @__p_0",
+WHERE `s`.`ParentId` = @__p_0
+LIMIT 1
+""",
Sql,
ignoreLineEndingDifferences: true);
}
@@ -41,6 +43,17 @@ protected override void RecordLog() =>
private string Sql { get; set; }
+ #region Expected JSON override
+
+ // TODO: Tiny discrepancy in decimal representation (Charge: 1.0000000000000000000000000000 instead of 1.00)
+ protected override string SerializedBlogs1
+ => base.SerializedBlogs1.Replace("1.00", "1.0000000000000000000000000000");
+
+ protected override string SerializedBlogs2
+ => base.SerializedBlogs2.Replace("1.00", "1.0000000000000000000000000000");
+
+ #endregion Expected JSON override
+
public class LoadSingleStoreFixture : LoadFixtureBase
{
public TestSqlLoggerFactory TestSqlLoggerFactory
diff --git a/test/EFCore.SingleStore.FunctionalTests/MaterializationInterceptionSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/MaterializationInterceptionSingleStoreTest.cs
index 6b2eb2e6a..22aebb204 100644
--- a/test/EFCore.SingleStore.FunctionalTests/MaterializationInterceptionSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/MaterializationInterceptionSingleStoreTest.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -6,11 +7,13 @@
using Microsoft.Extensions.DependencyInjection;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
using EntityFrameworkCore.SingleStore.Storage.Internal;
+using EntityFrameworkCore.SingleStore.Tests;
using Xunit;
namespace EntityFrameworkCore.SingleStore.FunctionalTests;
-public class MaterializationInterceptionSingleStoreTest : MaterializationInterceptionTestBase,
+public class MaterializationInterceptionSingleStoreTest :
+ MaterializationInterceptionTestBase,
IClassFixture
{
public MaterializationInterceptionSingleStoreTest(MaterializationInterceptionSingleStoreFixture fixture)
@@ -18,6 +21,52 @@ public MaterializationInterceptionSingleStoreTest(MaterializationInterceptionSin
{
}
+ [ConditionalTheory]
+ public override async Task Intercept_query_materialization_with_owned_types_projecting_collection(bool async)
+ {
+ // We're skipping this test when we're running tests on Managed Service due to the specifics of
+ // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
+ if (AppConfig.ManagedService)
+ {
+ return;
+ }
+
+ await base.Intercept_query_materialization_with_owned_types_projecting_collection(async);
+ }
+
+ public class SingleStoreLibraryContext : LibraryContext
+ {
+ public SingleStoreLibraryContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ // We're changing the data type of the fields from INT to BIGINT, because in SingleStore
+ // on a sharded (distributed) table, AUTO_INCREMENT can only be used on a BIGINT column
+ modelBuilder.Entity()
+ .Property(e => e.Id)
+ .HasColumnType("bigint");
+
+ modelBuilder.Entity().OwnsMany(
+ e => e.Settings, b =>
+ {
+ b.Property("Id").HasColumnType("bigint");
+ });
+
+ modelBuilder.Entity().OwnsMany(e => e.Settings);
+
+ // TODO: https://github.com/npgsql/efcore.pg/issues/2548
+ // modelBuilder.Entity().OwnsMany(e => e.Settings, b => b.ToJson());
+ }
+ }
+
+ public override LibraryContext CreateContext(IEnumerable interceptors, bool inject)
+ => new SingleStoreLibraryContext(Fixture.CreateOptions(interceptors, inject));
+
public class MaterializationInterceptionSingleStoreFixture : SingletonInterceptorsFixtureBase
{
protected override string StoreName
diff --git a/test/EFCore.SingleStore.FunctionalTests/MigrationSqlGeneratorSingleStoreTest.SingleStore.cs b/test/EFCore.SingleStore.FunctionalTests/MigrationSqlGeneratorSingleStoreTest.SingleStore.cs
index 6d31380d7..51c223ad7 100644
--- a/test/EFCore.SingleStore.FunctionalTests/MigrationSqlGeneratorSingleStoreTest.SingleStore.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/MigrationSqlGeneratorSingleStoreTest.SingleStore.cs
@@ -25,7 +25,7 @@ public virtual void DropUniqueConstraintOperation()
AssertSql(@"ALTER TABLE `Cars` DROP KEY `AK_Cars_LicensePlateNumber`;");
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public virtual void SingleStoreDropUniqueConstraintAndRecreateForeignKeysOperation_temporarily_drops_foreign_keys()
{
// A foreign key might reuse the alternate key for its own purposes and prohibit its deletion,
@@ -65,7 +65,7 @@ public virtual void DropPrimaryKeyOperation()
ALTER TABLE `Cars` DROP PRIMARY KEY;");
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public virtual void SingleStoreDropPrimaryKeyAndRecreateForeignKeysOperation_temporarily_drops_foreign_keys()
{
// A foreign key might reuse the primary key for its own purposes and prohibit its deletion,
diff --git a/test/EFCore.SingleStore.FunctionalTests/MigrationsSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/MigrationsSingleStoreTest.cs
index a2d67ffa6..7d8f2784f 100644
--- a/test/EFCore.SingleStore.FunctionalTests/MigrationsSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/MigrationsSingleStoreTest.cs
@@ -79,9 +79,9 @@ public override Task Alter_column_make_computed(bool? stored)
}
[ConditionalTheory(Skip = "TODO")]
- public override Task Add_column_computed_with_collation()
+ public override Task Add_column_computed_with_collation(bool stored)
{
- return base.Add_column_computed_with_collation();
+ return base.Add_column_computed_with_collation(stored);
}
[ConditionalFact(Skip = "BLOB/TEXT columns can't have a default value in SingleStore.")]
@@ -364,27 +364,7 @@ public override Task Alter_column_set_collation()
[SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
public override async Task Alter_sequence_all_settings()
{
- await Test(
- builder => builder.HasSequence("foo"),
- builder => { },
- builder => builder.HasSequence("foo")
- .StartsAt(-3)
- .IncrementsBy(2)
- .HasMin(-5)
- .HasMax(10)
- .IsCyclic(),
- model =>
- {
- var sequence = Assert.Single(model.Sequences);
-
- // Assert.Equal(-3, sequence.StartValue);
- Assert.Equal(1, sequence.StartValue); // Restarting doesn't change the scaffolded start value
-
- Assert.Equal(2, sequence.IncrementBy);
- Assert.Equal(-5, sequence.MinValue);
- Assert.Equal(10, sequence.MaxValue);
- Assert.True(sequence.IsCyclic);
- });
+ await base.Alter_sequence_all_settings();
AssertSql(
"""
@@ -392,7 +372,7 @@ await Test(
""",
//
"""
-ALTER SEQUENCE `foo` RESTART WITH -3;
+ALTER SEQUENCE `foo` START WITH -3 RESTART;
""");
}
@@ -402,6 +382,15 @@ public override Task Alter_sequence_increment_by()
return base.Alter_sequence_increment_by();
}
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override async Task Alter_sequence_restart_with()
+ {
+ await base.Alter_sequence_restart_with();
+
+ AssertSql(
+ @"ALTER SEQUENCE `foo` START WITH 3 RESTART;");
+ }
+
[ConditionalFact(Skip = "SingleStore's ALTER TABLE command doesn't work with comments")]
public override Task Alter_table_add_comment()
{
@@ -621,12 +610,35 @@ public override Task Move_table()
[SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
public override async Task Rename_sequence()
{
- await base.Rename_sequence();
+ if (OperatingSystem.IsWindows())
+ {
+ // On Windows, with `lower_case_table_names = 2`, renaming `TestSequence` to `testsequence` doesn't do anything, because
+ // `TestSequence` is internally being transformed to lower case, before it is processes further.
+ await Test(
+ builder => { },
+ builder => builder.HasSequence("TestSequence"),
+ builder => builder.HasSequence("testsequence2"),
+ builder => builder.RenameSequence(name: "TestSequence", newName: "testsequence2"),
+ model =>
+ {
+ var sequence = Assert.Single(model.Sequences);
+ Assert.Equal("testsequence2", sequence.Name);
+ });
- AssertSql(
-"""
-ALTER TABLE `TestSequence` RENAME `testsequence`;
-""");
+ AssertSql(
+ """
+ ALTER TABLE `TestSequence` RENAME `testsequence2`;
+ """);
+ }
+ else
+ {
+ await base.Rename_sequence();
+
+ AssertSql(
+ """
+ ALTER TABLE `TestSequence` RENAME `testsequence`;
+ """);
+ }
}
[ConditionalTheory(Skip = "TODO")]
@@ -718,7 +730,7 @@ await Test(
$@"ALTER TABLE `IceCream` ADD `Name` longtext COLLATE {DefaultCollation} NULL;");
}
- [ConditionalFact(Skip = "NVARCHAR data type isn't supported by SingleStore.")]
+ [ConditionalFact(Skip = "NVARCHAR data type isn't supported by SingleStore Distributed.")]
public virtual async Task Create_table_NVARCHAR_UPPERCASE_column()
{
await Test(
@@ -1075,7 +1087,7 @@ public virtual void Upgrade_legacy_charset_to_annotation_charset_only_does_not_g
Assert.Empty(operations);
}
- [ConditionalFact]
+ [ConditionalFact(Skip = "SingleStore works another way.")]
public virtual async Task Create_table_explicit_column_charset_takes_precedence_over_inherited_collation()
{
await Test(
@@ -1234,7 +1246,7 @@ await Test(
$@"ALTER TABLE `IceCream` MODIFY COLUMN `Brand` longtext CHARACTER SET {NonDefaultCharSet2} NULL;");
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public virtual async Task Drop_unique_constraint_without_recreating_foreign_keys()
{
await Test(
@@ -1266,7 +1278,7 @@ await Test(
@"ALTER TABLE `Foo` DROP KEY `AK_Foo_FooAK`;");
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public virtual async Task Drop_unique_constraint_without_recreating_foreign_keys_MigrationBuilder()
{
await Test(
@@ -1299,7 +1311,7 @@ await Test(
@"ALTER TABLE `Foo` DROP KEY `AK_Foo_FooAK`;");
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public virtual async Task Drop_unique_constraint_with_recreating_foreign_keys_MigrationBuilder()
{
await Test(
@@ -1336,7 +1348,7 @@ await Test(
@"ALTER TABLE `Foo` ADD CONSTRAINT `FK_Foo_Bar_BarFK` FOREIGN KEY (`BarFK`) REFERENCES `Bar` (`BarPK`) ON DELETE CASCADE;");
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override async Task Add_foreign_key()
{
await base.Add_foreign_key();
@@ -1347,25 +1359,25 @@ public override async Task Add_foreign_key()
@"ALTER TABLE `Orders` ADD CONSTRAINT `FK_Orders_Customers_CustomerId` FOREIGN KEY (`CustomerId`) REFERENCES `Customers` (`Id`) ON DELETE CASCADE;");
}
- [ConditionalFact(Skip = "Feature 'Check constraints' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'Check constraints' is not supported by SingleStore Distributed.")]
public override Task Add_check_constraint_with_name()
{
return base.Add_check_constraint_with_name();
}
- [ConditionalFact(Skip = "Feature 'Check constraints' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'Check constraints' is not supported by SingleStore Distributed.")]
public override Task Add_column_with_check_constraint()
{
return base.Add_column_with_check_constraint();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override Task Add_foreign_key_with_name()
{
return base.Add_foreign_key_with_name();
}
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
+ [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore Distributed.")]
public override Task Drop_foreign_key()
{
return base.Drop_foreign_key();
diff --git a/test/EFCore.SingleStore.FunctionalTests/ModelBuilding101SingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ModelBuilding101SingleStoreTest.cs
new file mode 100644
index 000000000..e88ff94fc
--- /dev/null
+++ b/test/EFCore.SingleStore.FunctionalTests/ModelBuilding101SingleStoreTest.cs
@@ -0,0 +1,10 @@
+using Microsoft.EntityFrameworkCore;
+using EntityFrameworkCore.SingleStore.Tests;
+
+namespace EntityFrameworkCore.SingleStore.FunctionalTests;
+
+public class ModelBuilding101SingleStoreTest : ModelBuilding101RelationalTestBase
+{
+ protected override DbContextOptionsBuilder ConfigureContext(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.UseSingleStore();
+}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/CaseSensitiveNorthwindQuerySingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/Query/CaseSensitiveNorthwindQuerySingleStoreFixture.cs
index 5fc9626d6..7fbbf7a5d 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/CaseSensitiveNorthwindQuerySingleStoreFixture.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/CaseSensitiveNorthwindQuerySingleStoreFixture.cs
@@ -1,12 +1,11 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
{
- public class CaseSensitiveNorthwindQuerySingleStoreFixture : NorthwindQueryRelationalFixture
+ public class CaseSensitiveNorthwindQuerySingleStoreFixture : NorthwindQuerySingleStoreFixture
where TModelCustomizer : IModelCustomizer, new()
{
protected override string StoreName => "NorthwindCs";
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySingleStoreTest.cs
index d33afbce7..045e19e5f 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySingleStoreTest.cs
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
using EntityFrameworkCore.SingleStore.Infrastructure;
using EntityFrameworkCore.SingleStore.Tests;
using EntityFrameworkCore.SingleStore.Tests.TestUtilities.Attributes;
@@ -132,13 +133,15 @@ public override async Task Include_collection_with_conditional_order_by(bool asy
await base.Include_collection_with_conditional_order_by(async);
AssertSql(
- @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`
+"""
+SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`
FROM `LevelOne` AS `l`
LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
ORDER BY CASE
- WHEN `l`.`Name` IS NOT NULL AND (`l`.`Name` LIKE '%03') THEN 1
+ WHEN `l`.`Name` LIKE '%03' THEN 1
ELSE 2
-END, `l`.`Id`");
+END, `l`.`Id`
+""");
}
public override async Task Multiple_complex_include_select(bool async)
@@ -819,8 +822,32 @@ public override async Task LeftJoin_with_Any_on_outer_source_and_projecting_coll
{
await base.LeftJoin_with_Any_on_outer_source_and_projecting_collection_from_inner(async);
- AssertSql(
- @"SELECT CASE
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `l0`.`Id` IS NULL THEN 0
+ ELSE `l0`.`Id`
+END, `l`.`Id`, `l0`.`Id`, `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
+FROM `LevelOne` AS `l`
+LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Required_Id`
+LEFT JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Required_Inverse3Id`
+WHERE EXISTS (
+ SELECT 1
+ FROM JSON_TABLE('["L1 01","L1 02"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longtext PATH '$[0]'
+ )) AS `v`
+ WHERE (`v`.`value` = `l`.`Name`) OR (`v`.`value` IS NULL AND (`l`.`Name` IS NULL)))
+ORDER BY `l`.`Id`, `l0`.`Id`
+""");
+ }
+ else
+ {
+ AssertSql(
+"""
+SELECT CASE
WHEN `l0`.`Id` IS NULL THEN 0
ELSE `l0`.`Id`
END, `l`.`Id`, `l0`.`Id`, `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
@@ -828,7 +855,9 @@ public override async Task LeftJoin_with_Any_on_outer_source_and_projecting_coll
LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Required_Id`
LEFT JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Required_Inverse3Id`
WHERE `l`.`Name` IN ('L1 01', 'L1 02')
-ORDER BY `l`.`Id`, `l0`.`Id`");
+ORDER BY `l`.`Id`, `l0`.`Id`
+""");
+ }
}
public override async Task Select_subquery_single_nested_subquery(bool async)
@@ -1559,7 +1588,8 @@ public override async Task Filtered_include_Skip_Take_with_another_Skip_Take_on_
await base.Filtered_include_Skip_Take_with_another_Skip_Take_on_top_level(async);
AssertSql(
- @"@__p_1='5'
+"""
+@__p_1='5'
@__p_0='10'
SELECT `t`.`Id`, `t`.`Date`, `t`.`Name`, `t`.`OneToMany_Optional_Self_Inverse1Id`, `t`.`OneToMany_Required_Self_Inverse1Id`, `t`.`OneToOne_Optional_Self1Id`, `t0`.`Id`, `t0`.`Date`, `t0`.`Level1_Optional_Id`, `t0`.`Level1_Required_Id`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`OneToMany_Optional_Self_Inverse2Id`, `t0`.`OneToMany_Required_Inverse2Id`, `t0`.`OneToMany_Required_Self_Inverse2Id`, `t0`.`OneToOne_Optional_PK_Inverse2Id`, `t0`.`OneToOne_Optional_Self2Id`, `t0`.`Id0`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Name0`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Optional_Self_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToMany_Required_Self_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`, `t0`.`OneToOne_Optional_Self3Id`
@@ -1576,11 +1606,12 @@ LEFT JOIN LATERAL (
FROM `LevelTwo` AS `l1`
WHERE `t`.`Id` = `l1`.`OneToMany_Optional_Inverse2Id`
ORDER BY `l1`.`Name` DESC
- LIMIT 4 OFFSET 2
+ LIMIT 4 OFFSET 1
) AS `t1`
LEFT JOIN `LevelThree` AS `l0` ON `t1`.`Id` = `l0`.`Level2_Optional_Id`
) AS `t0` ON TRUE
-ORDER BY `t`.`Id` DESC, `t0`.`Name` DESC, `t0`.`Id`");
+ORDER BY `t`.`Id` DESC, `t0`.`Name` DESC, `t0`.`Id`
+""");
}
public override async Task Skip_Take_on_grouping_element_inside_collection_projection(bool async)
@@ -1588,7 +1619,8 @@ public override async Task Skip_Take_on_grouping_element_inside_collection_proje
await base.Skip_Take_on_grouping_element_inside_collection_projection(async);
AssertSql(
- @"SELECT `l`.`Id`, `t2`.`Date`, `t2`.`Id`, `t2`.`Date0`, `t2`.`Name`, `t2`.`OneToMany_Optional_Self_Inverse1Id`, `t2`.`OneToMany_Required_Self_Inverse1Id`, `t2`.`OneToOne_Optional_Self1Id`
+"""
+SELECT `l`.`Id`, `t2`.`Date`, `t2`.`Id`, `t2`.`Date0`, `t2`.`Name`, `t2`.`OneToMany_Optional_Self_Inverse1Id`, `t2`.`OneToMany_Required_Self_Inverse1Id`, `t2`.`OneToOne_Optional_Self1Id`
FROM `LevelOne` AS `l`
LEFT JOIN LATERAL (
SELECT `t`.`Date`, `t0`.`Id`, `t0`.`Date` AS `Date0`, `t0`.`Name`, `t0`.`OneToMany_Optional_Self_Inverse1Id`, `t0`.`OneToMany_Required_Self_Inverse1Id`, `t0`.`OneToOne_Optional_Self1Id`
@@ -1608,7 +1640,8 @@ LEFT JOIN (
WHERE (1 < `t1`.`row`) AND (`t1`.`row` <= 6)
) AS `t0` ON `t`.`Date` = `t0`.`Date`
) AS `t2` ON TRUE
-ORDER BY `l`.`Id`, `t2`.`Date`, `t2`.`Date0`, `t2`.`Name`");
+ORDER BY `l`.`Id`, `t2`.`Date`, `t2`.`Date0`, `t2`.`Name`
+""");
}
public override async Task Skip_Take_on_grouping_element_with_collection_include(bool async)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySingleStoreTest.cs
index 5712b0b62..e2119716c 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySingleStoreTest.cs
@@ -1,5 +1,7 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using EntityFrameworkCore.SingleStore.Tests;
using Xunit;
using Xunit.Abstractions;
@@ -1492,20 +1494,24 @@ public override async Task Include_collection_with_conditional_order_by(bool asy
await base.Include_collection_with_conditional_order_by(async);
AssertSql(
- @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`
+"""
+SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`
FROM `LevelOne` AS `l`
ORDER BY CASE
- WHEN `l`.`Name` IS NOT NULL AND (`l`.`Name` LIKE '%03') THEN 1
+ WHEN `l`.`Name` LIKE '%03' THEN 1
ELSE 2
-END, `l`.`Id`",
- //
- @"SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `l`.`Id`
+END, `l`.`Id`
+""",
+//
+"""
+SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `l`.`Id`
FROM `LevelOne` AS `l`
INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
ORDER BY CASE
- WHEN `l`.`Name` IS NOT NULL AND (`l`.`Name` LIKE '%03') THEN 1
+ WHEN `l`.`Name` LIKE '%03' THEN 1
ELSE 2
-END, `l`.`Id`");
+END, `l`.`Id`
+""");
}
public override async Task Multiple_complex_include_select(bool async)
@@ -2299,22 +2305,64 @@ public override async Task LeftJoin_with_Any_on_outer_source_and_projecting_coll
{
await base.LeftJoin_with_Any_on_outer_source_and_projecting_collection_from_inner(async);
- AssertSql(
- @"SELECT CASE
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `l0`.`Id` IS NULL THEN 0
+ ELSE `l0`.`Id`
+END, `l`.`Id`, `l0`.`Id`
+FROM `LevelOne` AS `l`
+LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Required_Id`
+WHERE EXISTS (
+ SELECT 1
+ FROM JSON_TABLE('["L1 01","L1 02"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longtext PATH '$[0]'
+ )) AS `v`
+ WHERE (`v`.`value` = `l`.`Name`) OR (`v`.`value` IS NULL AND (`l`.`Name` IS NULL)))
+ORDER BY `l`.`Id`, `l0`.`Id`
+""",
+ //
+ """
+SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `l0`.`Id`
+FROM `LevelOne` AS `l`
+LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Required_Id`
+INNER JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Required_Inverse3Id`
+WHERE EXISTS (
+ SELECT 1
+ FROM JSON_TABLE('["L1 01","L1 02"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longtext PATH '$[0]'
+ )) AS `v`
+ WHERE (`v`.`value` = `l`.`Name`) OR (`v`.`value` IS NULL AND (`l`.`Name` IS NULL)))
+ORDER BY `l`.`Id`, `l0`.`Id`
+""");
+ }
+ else
+ {
+ AssertSql(
+"""
+SELECT CASE
WHEN `l0`.`Id` IS NULL THEN 0
ELSE `l0`.`Id`
END, `l`.`Id`, `l0`.`Id`
FROM `LevelOne` AS `l`
LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Required_Id`
WHERE `l`.`Name` IN ('L1 01', 'L1 02')
-ORDER BY `l`.`Id`, `l0`.`Id`",
+ORDER BY `l`.`Id`, `l0`.`Id`
+""",
//
- @"SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `l0`.`Id`
+ """
+SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `l0`.`Id`
FROM `LevelOne` AS `l`
LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Required_Id`
INNER JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Required_Inverse3Id`
WHERE `l`.`Name` IN ('L1 01', 'L1 02')
-ORDER BY `l`.`Id`, `l0`.`Id`");
+ORDER BY `l`.`Id`, `l0`.`Id`
+""");
+ }
}
public override async Task Filtered_include_different_filter_set_on_same_navigation_twice(bool async)
@@ -2415,15 +2463,18 @@ public override async Task Filtered_include_Skip_Take_with_another_Skip_Take_on_
await base.Filtered_include_Skip_Take_with_another_Skip_Take_on_top_level(async);
AssertSql(
- @"@__p_1='5'
+"""
+@__p_1='5'
@__p_0='10'
SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`
FROM `LevelOne` AS `l`
ORDER BY `l`.`Id` DESC
-LIMIT @__p_1 OFFSET @__p_0",
+LIMIT @__p_1 OFFSET @__p_0
+""",
//
- @"@__p_1='5'
+"""
+@__p_1='5'
@__p_0='10'
SELECT `t0`.`Id`, `t0`.`Date`, `t0`.`Level1_Optional_Id`, `t0`.`Level1_Required_Id`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`OneToMany_Optional_Self_Inverse2Id`, `t0`.`OneToMany_Required_Inverse2Id`, `t0`.`OneToMany_Required_Self_Inverse2Id`, `t0`.`OneToOne_Optional_PK_Inverse2Id`, `t0`.`OneToOne_Optional_Self2Id`, `t0`.`Id0`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Name0`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Optional_Self_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToMany_Required_Self_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`, `t0`.`OneToOne_Optional_Self3Id`, `t`.`Id`
@@ -2440,11 +2491,12 @@ JOIN LATERAL (
FROM `LevelTwo` AS `l1`
WHERE `t`.`Id` = `l1`.`OneToMany_Optional_Inverse2Id`
ORDER BY `l1`.`Name` DESC
- LIMIT 4 OFFSET 2
+ LIMIT 4 OFFSET 1
) AS `t1`
LEFT JOIN `LevelThree` AS `l0` ON `t1`.`Id` = `l0`.`Level2_Optional_Id`
) AS `t0` ON TRUE
-ORDER BY `t`.`Id` DESC, `t0`.`Name` DESC");
+ORDER BY `t`.`Id` DESC, `t0`.`Name` DESC
+""");
}
public override async Task Skip_Take_Distinct_on_grouping_element(bool async)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsQuerySingleStoreTest.cs
index 5fa01a23d..95e97479f 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsQuerySingleStoreTest.cs
@@ -5,7 +5,6 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel;
using EntityFrameworkCore.SingleStore.Infrastructure;
-using EntityFrameworkCore.SingleStore.Tests;
using EntityFrameworkCore.SingleStore.Tests.TestUtilities.Attributes;
using Xunit;
using Xunit.Abstractions;
@@ -25,19 +24,19 @@ public ComplexNavigationsQuerySingleStoreTest(ComplexNavigationsQuerySingleStore
protected override bool CanExecuteQueryString
=> true;
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Collection_FirstOrDefault_property_accesses_in_projection(bool async)
{
return base.Collection_FirstOrDefault_property_accesses_in_projection(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Contains_over_optional_navigation_with_null_column(bool async)
{
return base.Contains_over_optional_navigation_with_null_column(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Contains_over_optional_navigation_with_null_entity_reference(bool async)
{
return base.Contains_over_optional_navigation_with_null_entity_reference(async);
@@ -91,7 +90,7 @@ public override Task OrderBy_collection_count_ThenBy_reference_navigation(bool a
return base.OrderBy_collection_count_ThenBy_reference_navigation(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Project_collection_navigation_count(bool async)
{
return base.Project_collection_navigation_count(async);
@@ -103,7 +102,7 @@ public override Task Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(
return base.Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Where_navigation_property_to_collection(bool async)
{
return base.Where_navigation_property_to_collection(async);
@@ -177,6 +176,19 @@ JOIN LATERAL (
) AS `t0` ON TRUE");
}
+ public override async Task Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(bool async)
+ {
+ await base.Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(async);
+
+ AssertSql(
+ """
+ SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`
+ FROM `LevelOne` AS `l`
+ LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`Level1_Optional_Id`
+ WHERE `l0`.`Name` IS NOT NULL AND (LEFT(`l0`.`Name`, CHAR_LENGTH(`l0`.`Name`)) = `l0`.`Name`)
+ """);
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySingleStoreTest.cs
index 5f06bc792..482421771 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySingleStoreTest.cs
@@ -27,7 +27,7 @@ public ComplexNavigationsSharedTypeQuerySingleStoreTest(
// Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- [ConditionalTheory(Skip = "Feature 'Scalar subselect where outer table is not a sharded table' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Scalar subselect where outer table is not a sharded table' is not supported by SingleStore Distributed")]
public override Task Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key(bool async)
{
return base.Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key(async);
@@ -65,19 +65,19 @@ public override Task Element_selector_with_coalesce_repeated_in_aggregate(bool a
return base.Element_selector_with_coalesce_repeated_in_aggregate(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Collection_FirstOrDefault_property_accesses_in_projection(bool async)
{
return base.Collection_FirstOrDefault_property_accesses_in_projection(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Contains_over_optional_navigation_with_null_column(bool async)
{
return base.Contains_over_optional_navigation_with_null_column(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Contains_over_optional_navigation_with_null_entity_reference(bool async)
{
return base.Contains_over_optional_navigation_with_null_entity_reference(async);
@@ -184,6 +184,23 @@ await Assert.ThrowsAsync(
AssertSql();
}
+ public override async Task Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(bool async)
+ {
+ await base.Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(async);
+
+ AssertSql(
+ """
+ SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`
+ FROM `Level1` AS `l`
+ LEFT JOIN (
+ SELECT `l0`.`Level1_Optional_Id`, `l0`.`Level2_Name`
+ FROM `Level1` AS `l0`
+ WHERE (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL
+ ) AS `t` ON `l`.`Id` = `t`.`Level1_Optional_Id`
+ WHERE `t`.`Level2_Name` IS NOT NULL AND (LEFT(`t`.`Level2_Name`, CHAR_LENGTH(`t`.`Level2_Name`)) = `t`.`Level2_Name`)
+ """);
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/ComplexTypeQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexTypeQuerySingleStoreTest.cs
new file mode 100644
index 000000000..0a80bef5e
--- /dev/null
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/ComplexTypeQuerySingleStoreTest.cs
@@ -0,0 +1,751 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query;
+
+public class ComplexTypeQuerySingleStoreTest : ComplexTypeQueryRelationalTestBase<
+ ComplexTypeQuerySingleStoreTest.ComplexTypeQuerySingleStoreFixture>
+{
+ public ComplexTypeQuerySingleStoreTest(ComplexTypeQuerySingleStoreFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ public override async Task Filter_on_property_inside_complex_type(bool async)
+ {
+ await base.Filter_on_property_inside_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Filter_on_property_inside_nested_complex_type(bool async)
+ {
+ await base.Filter_on_property_inside_nested_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`ShippingAddress_Country_Code` = 'DE'
+""");
+ }
+
+ public override async Task Filter_on_property_inside_complex_type_after_subquery(bool async)
+ {
+ await base.Filter_on_property_inside_complex_type_after_subquery(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+SELECT DISTINCT `t`.`Id`, `t`.`Name`, `t`.`BillingAddress_AddressLine1`, `t`.`BillingAddress_AddressLine2`, `t`.`BillingAddress_ZipCode`, `t`.`BillingAddress_Country_Code`, `t`.`BillingAddress_Country_FullName`, `t`.`ShippingAddress_AddressLine1`, `t`.`ShippingAddress_AddressLine2`, `t`.`ShippingAddress_ZipCode`, `t`.`ShippingAddress_Country_Code`, `t`.`ShippingAddress_Country_FullName`
+FROM (
+ SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+ FROM `Customer` AS `c`
+ ORDER BY `c`.`Id`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+WHERE `t`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Filter_on_property_inside_nested_complex_type_after_subquery(bool async)
+ {
+ await base.Filter_on_property_inside_nested_complex_type_after_subquery(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+SELECT DISTINCT `t`.`Id`, `t`.`Name`, `t`.`BillingAddress_AddressLine1`, `t`.`BillingAddress_AddressLine2`, `t`.`BillingAddress_ZipCode`, `t`.`BillingAddress_Country_Code`, `t`.`BillingAddress_Country_FullName`, `t`.`ShippingAddress_AddressLine1`, `t`.`ShippingAddress_AddressLine2`, `t`.`ShippingAddress_ZipCode`, `t`.`ShippingAddress_Country_Code`, `t`.`ShippingAddress_Country_FullName`
+FROM (
+ SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+ FROM `Customer` AS `c`
+ ORDER BY `c`.`Id`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+WHERE `t`.`ShippingAddress_Country_Code` = 'DE'
+""");
+ }
+
+ public override async Task Filter_on_required_property_inside_required_complex_type_on_optional_navigation(bool async)
+ {
+ await base.Filter_on_required_property_inside_required_complex_type_on_optional_navigation(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`OptionalCustomerId`, `c`.`RequiredCustomerId`, `c0`.`Id`, `c0`.`Name`, `c0`.`BillingAddress_AddressLine1`, `c0`.`BillingAddress_AddressLine2`, `c0`.`BillingAddress_ZipCode`, `c0`.`BillingAddress_Country_Code`, `c0`.`BillingAddress_Country_FullName`, `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`, `c1`.`Id`, `c1`.`Name`, `c1`.`BillingAddress_AddressLine1`, `c1`.`BillingAddress_AddressLine2`, `c1`.`BillingAddress_ZipCode`, `c1`.`BillingAddress_Country_Code`, `c1`.`BillingAddress_Country_FullName`, `c1`.`ShippingAddress_AddressLine1`, `c1`.`ShippingAddress_AddressLine2`, `c1`.`ShippingAddress_ZipCode`, `c1`.`ShippingAddress_Country_Code`, `c1`.`ShippingAddress_Country_FullName`
+FROM `CustomerGroup` AS `c`
+LEFT JOIN `Customer` AS `c0` ON `c`.`OptionalCustomerId` = `c0`.`Id`
+INNER JOIN `Customer` AS `c1` ON `c`.`RequiredCustomerId` = `c1`.`Id`
+WHERE (`c0`.`ShippingAddress_ZipCode` <> 7728) OR `c0`.`ShippingAddress_ZipCode` IS NULL
+""");
+ }
+
+ public override async Task Filter_on_required_property_inside_required_complex_type_on_required_navigation(bool async)
+ {
+ await base.Filter_on_required_property_inside_required_complex_type_on_required_navigation(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`OptionalCustomerId`, `c`.`RequiredCustomerId`, `c1`.`Id`, `c1`.`Name`, `c1`.`BillingAddress_AddressLine1`, `c1`.`BillingAddress_AddressLine2`, `c1`.`BillingAddress_ZipCode`, `c1`.`BillingAddress_Country_Code`, `c1`.`BillingAddress_Country_FullName`, `c1`.`ShippingAddress_AddressLine1`, `c1`.`ShippingAddress_AddressLine2`, `c1`.`ShippingAddress_ZipCode`, `c1`.`ShippingAddress_Country_Code`, `c1`.`ShippingAddress_Country_FullName`, `c0`.`Id`, `c0`.`Name`, `c0`.`BillingAddress_AddressLine1`, `c0`.`BillingAddress_AddressLine2`, `c0`.`BillingAddress_ZipCode`, `c0`.`BillingAddress_Country_Code`, `c0`.`BillingAddress_Country_FullName`, `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`
+FROM `CustomerGroup` AS `c`
+INNER JOIN `Customer` AS `c0` ON `c`.`RequiredCustomerId` = `c0`.`Id`
+LEFT JOIN `Customer` AS `c1` ON `c`.`OptionalCustomerId` = `c1`.`Id`
+WHERE `c0`.`ShippingAddress_ZipCode` <> 7728
+""");
+ }
+
+ // This test fails because when OptionalCustomer is null, we get all-null results because of the LEFT JOIN, and we materialize this
+ // as an empty ShippingAddress instead of null (see SQL). The proper solution here would be to project the Customer ID just for the
+ // purpose of knowing that it's there.
+ public override async Task Project_complex_type_via_optional_navigation(bool async)
+ {
+ var exception = await Assert.ThrowsAsync(() => base.Project_complex_type_via_optional_navigation(async));
+
+ Assert.Equal(RelationalStrings.CannotProjectNullableComplexType("Customer.ShippingAddress#Address"), exception.Message);
+ }
+
+ public override async Task Project_complex_type_via_required_navigation(bool async)
+ {
+ await base.Project_complex_type_via_required_navigation(async);
+
+ AssertSql(
+"""
+SELECT `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`
+FROM `CustomerGroup` AS `c`
+INNER JOIN `Customer` AS `c0` ON `c`.`RequiredCustomerId` = `c0`.`Id`
+""");
+ }
+
+ public override async Task Load_complex_type_after_subquery_on_entity_type(bool async)
+ {
+ await base.Load_complex_type_after_subquery_on_entity_type(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+SELECT DISTINCT `t`.`Id`, `t`.`Name`, `t`.`BillingAddress_AddressLine1`, `t`.`BillingAddress_AddressLine2`, `t`.`BillingAddress_ZipCode`, `t`.`BillingAddress_Country_Code`, `t`.`BillingAddress_Country_FullName`, `t`.`ShippingAddress_AddressLine1`, `t`.`ShippingAddress_AddressLine2`, `t`.`ShippingAddress_ZipCode`, `t`.`ShippingAddress_Country_Code`, `t`.`ShippingAddress_Country_FullName`
+FROM (
+ SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+ FROM `Customer` AS `c`
+ ORDER BY `c`.`Id`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+""");
+ }
+
+ public override async Task Select_complex_type(bool async)
+ {
+ await base.Select_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+""");
+ }
+
+ public override async Task Select_nested_complex_type(bool async)
+ {
+ await base.Select_nested_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+""");
+ }
+
+ public override async Task Select_single_property_on_nested_complex_type(bool async)
+ {
+ await base.Select_single_property_on_nested_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+""");
+ }
+
+ public override async Task Select_complex_type_Where(bool async)
+ {
+ await base.Select_complex_type_Where(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Select_complex_type_Distinct(bool async)
+ {
+ await base.Select_complex_type_Distinct(async);
+
+ AssertSql(
+"""
+SELECT DISTINCT `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+""");
+ }
+
+ public override async Task Complex_type_equals_complex_type(bool async)
+ {
+ await base.Complex_type_equals_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE ((`c`.`ShippingAddress_AddressLine1` = `c`.`BillingAddress_AddressLine1`) AND ((`c`.`ShippingAddress_AddressLine2` = `c`.`BillingAddress_AddressLine2`) OR (`c`.`ShippingAddress_AddressLine2` IS NULL AND (`c`.`BillingAddress_AddressLine2` IS NULL)))) AND (`c`.`ShippingAddress_ZipCode` = `c`.`BillingAddress_ZipCode`)
+""");
+ }
+
+ public override async Task Complex_type_equals_constant(bool async)
+ {
+ await base.Complex_type_equals_constant(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE ((((`c`.`ShippingAddress_AddressLine1` = '804 S. Lakeshore Road') AND `c`.`ShippingAddress_AddressLine2` IS NULL) AND (`c`.`ShippingAddress_ZipCode` = 38654)) AND (`c`.`ShippingAddress_Country_Code` = 'US')) AND (`c`.`ShippingAddress_Country_FullName` = 'United States')
+""");
+ }
+
+ public override async Task Complex_type_equals_parameter(bool async)
+ {
+ await base.Complex_type_equals_parameter(async);
+
+ AssertSql(
+"""
+@__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' (Size = 4000)
+@__entity_equality_address_0_ZipCode='38654' (Nullable = true)
+@__entity_equality_address_0_Code='US' (Size = 4000)
+@__entity_equality_address_0_FullName='United States' (Size = 4000)
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE ((((`c`.`ShippingAddress_AddressLine1` = @__entity_equality_address_0_AddressLine1) AND `c`.`ShippingAddress_AddressLine2` IS NULL) AND (`c`.`ShippingAddress_ZipCode` = @__entity_equality_address_0_ZipCode)) AND (`c`.`ShippingAddress_Country_Code` = @__entity_equality_address_0_Code)) AND (`c`.`ShippingAddress_Country_FullName` = @__entity_equality_address_0_FullName)
+""");
+ }
+
+ public override async Task Subquery_over_complex_type(bool async)
+ {
+ await base.Subquery_over_complex_type(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Contains_over_complex_type(bool async)
+ {
+ await base.Contains_over_complex_type(async);
+
+ AssertSql(
+"""
+@__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' (Size = 4000)
+@__entity_equality_address_0_ZipCode='38654' (Nullable = true)
+@__entity_equality_address_0_Code='US' (Size = 4000)
+@__entity_equality_address_0_FullName='United States' (Size = 4000)
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE EXISTS (
+ SELECT 1
+ FROM `Customer` AS `c0`
+ WHERE ((((`c0`.`ShippingAddress_AddressLine1` = @__entity_equality_address_0_AddressLine1) AND `c0`.`ShippingAddress_AddressLine2` IS NULL) AND (`c0`.`ShippingAddress_ZipCode` = @__entity_equality_address_0_ZipCode)) AND (`c0`.`ShippingAddress_Country_Code` = @__entity_equality_address_0_Code)) AND (`c0`.`ShippingAddress_Country_FullName` = @__entity_equality_address_0_FullName))
+""");
+ }
+
+ public override async Task Concat_complex_type(bool async)
+ {
+ await base.Concat_complex_type(async);
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`Id` = 1
+UNION ALL
+SELECT `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c0`
+WHERE `c0`.`Id` = 2
+""");
+ }
+
+ public override async Task Concat_entity_type_containing_complex_property(bool async)
+ {
+ await base.Concat_entity_type_containing_complex_property(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`Id` = 1
+UNION ALL
+SELECT `c0`.`Id`, `c0`.`Name`, `c0`.`BillingAddress_AddressLine1`, `c0`.`BillingAddress_AddressLine2`, `c0`.`BillingAddress_ZipCode`, `c0`.`BillingAddress_Country_Code`, `c0`.`BillingAddress_Country_FullName`, `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c0`
+WHERE `c0`.`Id` = 2
+""");
+ }
+
+ public override async Task Union_entity_type_containing_complex_property(bool async)
+ {
+ await base.Union_entity_type_containing_complex_property(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`Name`, `c`.`BillingAddress_AddressLine1`, `c`.`BillingAddress_AddressLine2`, `c`.`BillingAddress_ZipCode`, `c`.`BillingAddress_Country_Code`, `c`.`BillingAddress_Country_FullName`, `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`Id` = 1
+UNION
+SELECT `c0`.`Id`, `c0`.`Name`, `c0`.`BillingAddress_AddressLine1`, `c0`.`BillingAddress_AddressLine2`, `c0`.`BillingAddress_ZipCode`, `c0`.`BillingAddress_Country_Code`, `c0`.`BillingAddress_Country_FullName`, `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c0`
+WHERE `c0`.`Id` = 2
+""");
+ }
+
+ public override async Task Union_complex_type(bool async)
+ {
+ await base.Union_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_AddressLine1`, `c`.`ShippingAddress_AddressLine2`, `c`.`ShippingAddress_ZipCode`, `c`.`ShippingAddress_Country_Code`, `c`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c`
+WHERE `c`.`Id` = 1
+UNION
+SELECT `c0`.`ShippingAddress_AddressLine1`, `c0`.`ShippingAddress_AddressLine2`, `c0`.`ShippingAddress_ZipCode`, `c0`.`ShippingAddress_Country_Code`, `c0`.`ShippingAddress_Country_FullName`
+FROM `Customer` AS `c0`
+WHERE `c0`.`Id` = 2
+""");
+ }
+
+ public override async Task Concat_property_in_complex_type(bool async)
+ {
+ await base.Concat_property_in_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_AddressLine1`
+FROM `Customer` AS `c`
+UNION ALL
+SELECT `c0`.`BillingAddress_AddressLine1` AS `ShippingAddress_AddressLine1`
+FROM `Customer` AS `c0`
+""");
+ }
+
+ public override async Task Union_property_in_complex_type(bool async)
+ {
+ await base.Union_property_in_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `c`.`ShippingAddress_AddressLine1`
+FROM `Customer` AS `c`
+UNION
+SELECT `c0`.`BillingAddress_AddressLine1` AS `ShippingAddress_AddressLine1`
+FROM `Customer` AS `c0`
+""");
+ }
+
+ public override async Task Concat_two_different_complex_type(bool async)
+ {
+ await base.Concat_two_different_complex_type(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Union_two_different_complex_type(bool async)
+ {
+ await base.Union_two_different_complex_type(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Complex_type_equals_null(bool async)
+ {
+ await base.Complex_type_equals_null(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Subquery_over_struct_complex_type(bool async)
+ {
+ await base.Subquery_over_struct_complex_type(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Concat_two_different_struct_complex_type(bool async)
+ {
+ await base.Concat_two_different_struct_complex_type(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Union_two_different_struct_complex_type(bool async)
+ {
+ await base.Union_two_different_struct_complex_type(async);
+
+ AssertSql(
+);
+ }
+
+ public override async Task Filter_on_property_inside_struct_complex_type(bool async)
+ {
+ await base.Filter_on_property_inside_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Filter_on_property_inside_nested_struct_complex_type(bool async)
+ {
+ await base.Filter_on_property_inside_nested_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`ShippingAddress_Country_Code` = 'DE'
+""");
+ }
+
+ public override async Task Filter_on_property_inside_struct_complex_type_after_subquery(bool async)
+ {
+ await base.Filter_on_property_inside_struct_complex_type_after_subquery(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+SELECT DISTINCT `t`.`Id`, `t`.`Name`, `t`.`BillingAddress_AddressLine1`, `t`.`BillingAddress_AddressLine2`, `t`.`BillingAddress_ZipCode`, `t`.`BillingAddress_Country_Code`, `t`.`BillingAddress_Country_FullName`, `t`.`ShippingAddress_AddressLine1`, `t`.`ShippingAddress_AddressLine2`, `t`.`ShippingAddress_ZipCode`, `t`.`ShippingAddress_Country_Code`, `t`.`ShippingAddress_Country_FullName`
+FROM (
+ SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+ FROM `ValuedCustomer` AS `v`
+ ORDER BY `v`.`Id`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+WHERE `t`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Filter_on_property_inside_nested_struct_complex_type_after_subquery(bool async)
+ {
+ await base.Filter_on_property_inside_nested_struct_complex_type_after_subquery(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+SELECT DISTINCT `t`.`Id`, `t`.`Name`, `t`.`BillingAddress_AddressLine1`, `t`.`BillingAddress_AddressLine2`, `t`.`BillingAddress_ZipCode`, `t`.`BillingAddress_Country_Code`, `t`.`BillingAddress_Country_FullName`, `t`.`ShippingAddress_AddressLine1`, `t`.`ShippingAddress_AddressLine2`, `t`.`ShippingAddress_ZipCode`, `t`.`ShippingAddress_Country_Code`, `t`.`ShippingAddress_Country_FullName`
+FROM (
+ SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+ FROM `ValuedCustomer` AS `v`
+ ORDER BY `v`.`Id`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+WHERE `t`.`ShippingAddress_Country_Code` = 'DE'
+""");
+ }
+
+ public override async Task Filter_on_required_property_inside_required_struct_complex_type_on_optional_navigation(bool async)
+ {
+ await base.Filter_on_required_property_inside_required_struct_complex_type_on_optional_navigation(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`OptionalCustomerId`, `v`.`RequiredCustomerId`, `v0`.`Id`, `v0`.`Name`, `v0`.`BillingAddress_AddressLine1`, `v0`.`BillingAddress_AddressLine2`, `v0`.`BillingAddress_ZipCode`, `v0`.`BillingAddress_Country_Code`, `v0`.`BillingAddress_Country_FullName`, `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`, `v1`.`Id`, `v1`.`Name`, `v1`.`BillingAddress_AddressLine1`, `v1`.`BillingAddress_AddressLine2`, `v1`.`BillingAddress_ZipCode`, `v1`.`BillingAddress_Country_Code`, `v1`.`BillingAddress_Country_FullName`, `v1`.`ShippingAddress_AddressLine1`, `v1`.`ShippingAddress_AddressLine2`, `v1`.`ShippingAddress_ZipCode`, `v1`.`ShippingAddress_Country_Code`, `v1`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomerGroup` AS `v`
+LEFT JOIN `ValuedCustomer` AS `v0` ON `v`.`OptionalCustomerId` = `v0`.`Id`
+INNER JOIN `ValuedCustomer` AS `v1` ON `v`.`RequiredCustomerId` = `v1`.`Id`
+WHERE (`v0`.`ShippingAddress_ZipCode` <> 7728) OR `v0`.`ShippingAddress_ZipCode` IS NULL
+""");
+ }
+
+ public override async Task Filter_on_required_property_inside_required_struct_complex_type_on_required_navigation(bool async)
+ {
+ await base.Filter_on_required_property_inside_required_struct_complex_type_on_required_navigation(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`OptionalCustomerId`, `v`.`RequiredCustomerId`, `v1`.`Id`, `v1`.`Name`, `v1`.`BillingAddress_AddressLine1`, `v1`.`BillingAddress_AddressLine2`, `v1`.`BillingAddress_ZipCode`, `v1`.`BillingAddress_Country_Code`, `v1`.`BillingAddress_Country_FullName`, `v1`.`ShippingAddress_AddressLine1`, `v1`.`ShippingAddress_AddressLine2`, `v1`.`ShippingAddress_ZipCode`, `v1`.`ShippingAddress_Country_Code`, `v1`.`ShippingAddress_Country_FullName`, `v0`.`Id`, `v0`.`Name`, `v0`.`BillingAddress_AddressLine1`, `v0`.`BillingAddress_AddressLine2`, `v0`.`BillingAddress_ZipCode`, `v0`.`BillingAddress_Country_Code`, `v0`.`BillingAddress_Country_FullName`, `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomerGroup` AS `v`
+INNER JOIN `ValuedCustomer` AS `v0` ON `v`.`RequiredCustomerId` = `v0`.`Id`
+LEFT JOIN `ValuedCustomer` AS `v1` ON `v`.`OptionalCustomerId` = `v1`.`Id`
+WHERE `v0`.`ShippingAddress_ZipCode` <> 7728
+""");
+ }
+
+ // This test fails because when OptionalCustomer is null, we get all-null results because of the LEFT JOIN, and we materialize this
+ // as an empty ShippingAddress instead of null (see SQL). The proper solution here would be to project the Customer ID just for the
+ // purpose of knowing that it's there.
+ public override async Task Project_struct_complex_type_via_optional_navigation(bool async)
+ {
+ var exception = await Assert.ThrowsAsync(() => base.Project_struct_complex_type_via_optional_navigation(async));
+
+ Assert.Equal(RelationalStrings.CannotProjectNullableComplexType("ValuedCustomer.ShippingAddress#AddressStruct"), exception.Message);
+ }
+
+ public override async Task Project_struct_complex_type_via_required_navigation(bool async)
+ {
+ await base.Project_struct_complex_type_via_required_navigation(async);
+AssertSql(
+"""
+SELECT `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomerGroup` AS `v`
+INNER JOIN `ValuedCustomer` AS `v0` ON `v`.`RequiredCustomerId` = `v0`.`Id`
+""");
+ }
+
+ public override async Task Load_struct_complex_type_after_subquery_on_entity_type(bool async)
+ {
+ await base.Load_struct_complex_type_after_subquery_on_entity_type(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+SELECT DISTINCT `t`.`Id`, `t`.`Name`, `t`.`BillingAddress_AddressLine1`, `t`.`BillingAddress_AddressLine2`, `t`.`BillingAddress_ZipCode`, `t`.`BillingAddress_Country_Code`, `t`.`BillingAddress_Country_FullName`, `t`.`ShippingAddress_AddressLine1`, `t`.`ShippingAddress_AddressLine2`, `t`.`ShippingAddress_ZipCode`, `t`.`ShippingAddress_Country_Code`, `t`.`ShippingAddress_Country_FullName`
+FROM (
+ SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+ FROM `ValuedCustomer` AS `v`
+ ORDER BY `v`.`Id`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+""");
+ }
+
+ public override async Task Select_struct_complex_type(bool async)
+ {
+ await base.Select_struct_complex_type(async);
+AssertSql(
+"""
+SELECT `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+""");
+ }
+
+ public override async Task Select_nested_struct_complex_type(bool async)
+ {
+ await base.Select_nested_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+""");
+ }
+
+ public override async Task Select_single_property_on_nested_struct_complex_type(bool async)
+ {
+ await base.Select_single_property_on_nested_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+""");
+ }
+
+ public override async Task Select_struct_complex_type_Where(bool async)
+ {
+ await base.Select_struct_complex_type_Where(async);
+AssertSql(
+"""
+SELECT `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`ShippingAddress_ZipCode` = 7728
+""");
+ }
+
+ public override async Task Select_struct_complex_type_Distinct(bool async)
+ {
+ await base.Select_struct_complex_type_Distinct(async);
+AssertSql(
+"""
+SELECT DISTINCT `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+""");
+ }
+
+ public override async Task Struct_complex_type_equals_struct_complex_type(bool async)
+ {
+ await base.Struct_complex_type_equals_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE ((`v`.`ShippingAddress_AddressLine1` = `v`.`BillingAddress_AddressLine1`) AND ((`v`.`ShippingAddress_AddressLine2` = `v`.`BillingAddress_AddressLine2`) OR (`v`.`ShippingAddress_AddressLine2` IS NULL AND (`v`.`BillingAddress_AddressLine2` IS NULL)))) AND (`v`.`ShippingAddress_ZipCode` = `v`.`BillingAddress_ZipCode`)
+""");
+ }
+
+ public override async Task Struct_complex_type_equals_constant(bool async)
+ {
+ await base.Struct_complex_type_equals_constant(async);
+AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE ((((`v`.`ShippingAddress_AddressLine1` = '804 S. Lakeshore Road') AND `v`.`ShippingAddress_AddressLine2` IS NULL) AND (`v`.`ShippingAddress_ZipCode` = 38654)) AND (`v`.`ShippingAddress_Country_Code` = 'US')) AND (`v`.`ShippingAddress_Country_FullName` = 'United States')
+""");
+ }
+
+ public override async Task Struct_complex_type_equals_parameter(bool async)
+ {
+ await base.Struct_complex_type_equals_parameter(async);
+
+ AssertSql(
+"""
+@__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' (Size = 4000)
+@__entity_equality_address_0_ZipCode='38654' (Nullable = true)
+@__entity_equality_address_0_Code='US' (Size = 4000)
+@__entity_equality_address_0_FullName='United States' (Size = 4000)
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE ((((`v`.`ShippingAddress_AddressLine1` = @__entity_equality_address_0_AddressLine1) AND `v`.`ShippingAddress_AddressLine2` IS NULL) AND (`v`.`ShippingAddress_ZipCode` = @__entity_equality_address_0_ZipCode)) AND (`v`.`ShippingAddress_Country_Code` = @__entity_equality_address_0_Code)) AND (`v`.`ShippingAddress_Country_FullName` = @__entity_equality_address_0_FullName)
+""");
+ }
+
+ public override async Task Contains_over_struct_complex_type(bool async)
+ {
+ await base.Contains_over_struct_complex_type(async);
+
+ AssertSql(
+"""
+@__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' (Size = 4000)
+@__entity_equality_address_0_ZipCode='38654' (Nullable = true)
+@__entity_equality_address_0_Code='US' (Size = 4000)
+@__entity_equality_address_0_FullName='United States' (Size = 4000)
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE EXISTS (
+ SELECT 1
+ FROM `ValuedCustomer` AS `v0`
+ WHERE ((((`v0`.`ShippingAddress_AddressLine1` = @__entity_equality_address_0_AddressLine1) AND `v0`.`ShippingAddress_AddressLine2` IS NULL) AND (`v0`.`ShippingAddress_ZipCode` = @__entity_equality_address_0_ZipCode)) AND (`v0`.`ShippingAddress_Country_Code` = @__entity_equality_address_0_Code)) AND (`v0`.`ShippingAddress_Country_FullName` = @__entity_equality_address_0_FullName))
+""");
+ }
+
+ public override async Task Concat_entity_type_containing_struct_complex_property(bool async)
+ {
+ await base.Concat_entity_type_containing_struct_complex_property(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`Id` = 1
+UNION ALL
+SELECT `v0`.`Id`, `v0`.`Name`, `v0`.`BillingAddress_AddressLine1`, `v0`.`BillingAddress_AddressLine2`, `v0`.`BillingAddress_ZipCode`, `v0`.`BillingAddress_Country_Code`, `v0`.`BillingAddress_Country_FullName`, `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v0`
+WHERE `v0`.`Id` = 2
+""");
+ }
+
+ public override async Task Union_entity_type_containing_struct_complex_property(bool async)
+ {
+ await base.Union_entity_type_containing_struct_complex_property(async);
+
+ AssertSql(
+"""
+SELECT `v`.`Id`, `v`.`Name`, `v`.`BillingAddress_AddressLine1`, `v`.`BillingAddress_AddressLine2`, `v`.`BillingAddress_ZipCode`, `v`.`BillingAddress_Country_Code`, `v`.`BillingAddress_Country_FullName`, `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`Id` = 1
+UNION
+SELECT `v0`.`Id`, `v0`.`Name`, `v0`.`BillingAddress_AddressLine1`, `v0`.`BillingAddress_AddressLine2`, `v0`.`BillingAddress_ZipCode`, `v0`.`BillingAddress_Country_Code`, `v0`.`BillingAddress_Country_FullName`, `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v0`
+WHERE `v0`.`Id` = 2
+""");
+ }
+
+ public override async Task Concat_struct_complex_type(bool async)
+ {
+ await base.Concat_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`Id` = 1
+UNION ALL
+SELECT `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v0`
+WHERE `v0`.`Id` = 2
+""");
+ }
+
+ public override async Task Union_struct_complex_type(bool async)
+ {
+ await base.Union_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`ShippingAddress_AddressLine1`, `v`.`ShippingAddress_AddressLine2`, `v`.`ShippingAddress_ZipCode`, `v`.`ShippingAddress_Country_Code`, `v`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v`
+WHERE `v`.`Id` = 1
+UNION
+SELECT `v0`.`ShippingAddress_AddressLine1`, `v0`.`ShippingAddress_AddressLine2`, `v0`.`ShippingAddress_ZipCode`, `v0`.`ShippingAddress_Country_Code`, `v0`.`ShippingAddress_Country_FullName`
+FROM `ValuedCustomer` AS `v0`
+WHERE `v0`.`Id` = 2
+""");
+ }
+
+ public override async Task Concat_property_in_struct_complex_type(bool async)
+ {
+ await base.Concat_property_in_struct_complex_type(async);
+AssertSql(
+"""
+SELECT `v`.`ShippingAddress_AddressLine1`
+FROM `ValuedCustomer` AS `v`
+UNION ALL
+SELECT `v0`.`BillingAddress_AddressLine1` AS `ShippingAddress_AddressLine1`
+FROM `ValuedCustomer` AS `v0`
+""");
+ }
+
+ public override async Task Union_property_in_struct_complex_type(bool async)
+ {
+ await base.Union_property_in_struct_complex_type(async);
+
+ AssertSql(
+"""
+SELECT `v`.`ShippingAddress_AddressLine1`
+FROM `ValuedCustomer` AS `v`
+UNION
+SELECT `v0`.`BillingAddress_AddressLine1` AS `ShippingAddress_AddressLine1`
+FROM `ValuedCustomer` AS `v0`
+""");
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ public class ComplexTypeQuerySingleStoreFixture : ComplexTypeQueryRelationalFixtureBase
+ {
+ protected override ITestStoreFactory TestStoreFactory
+ => SingleStoreTestStoreFactory.Instance;
+ }
+}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/Ef6GroupBySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/Ef6GroupBySingleStoreTest.cs
index aa1e3581b..21dab32fb 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/Ef6GroupBySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/Ef6GroupBySingleStoreTest.cs
@@ -752,7 +752,7 @@ LEFT JOIN (
""");
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override async Task Whats_new_2021_sample_7(bool async)
{
await base.Whats_new_2021_sample_7(async);
@@ -800,7 +800,7 @@ GROUP BY `p`.`Category`
""");
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override async Task Whats_new_2021_sample_9(bool async)
{
await base.Whats_new_2021_sample_9(async);
@@ -829,7 +829,7 @@ GROUP BY `p`.`Category`
""");
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override async Task Whats_new_2021_sample_4(bool async)
{
await base.Whats_new_2021_sample_4(async);
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreNoBackslashesTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreNoBackslashesTest.cs
index fbbf3aa72..40ea59c14 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreNoBackslashesTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreNoBackslashesTest.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using EntityFrameworkCore.SingleStore.Infrastructure.Internal;
using EntityFrameworkCore.SingleStore.Tests;
using Xunit;
using Xunit.Abstractions;
@@ -80,10 +81,30 @@ public override async Task Where_contains_query_escapes(bool async)
{
await base.Where_contains_query_escapes(async);
- AssertSql(
- @"SELECT `a`.`ArtistId`, `a`.`Name`
-FROM `Artists` AS `a`
-WHERE `a`.`Name` IN ('Back\slasher''s', 'John''s Chill Box')");
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+ """
+ SELECT `a`.`ArtistId`, `a`.`Name`
+ FROM `Artists` AS `a`
+ WHERE `a`.`Name` IN (
+ SELECT `a0`.`value`
+ FROM JSON_TABLE('["Back\\slasher\u0027s","John\u0027s Chill Box"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longtext PATH '$[0]'
+ )) AS `a0`
+ )
+ """);
+ }
+ else
+ {
+ AssertSql(
+ """
+ SELECT `a`.`ArtistId`, `a`.`Name`
+ FROM `Artists` AS `a`
+ WHERE `a`.`Name` IN ('Back\slasher''s', 'John''s Chill Box')
+ """);
+ }
}
public class EscapesSingleStoreNoBackslashesFixture : EscapesSingleStoreFixtureBase
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreTest.cs
index 40d8b09a6..a70ca3f03 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/EscapesSingleStoreTest.cs
@@ -79,10 +79,30 @@ public override async Task Where_contains_query_escapes(bool async)
{
await base.Where_contains_query_escapes(async);
- AssertSql(
- @"SELECT `a`.`ArtistId`, `a`.`Name`
-FROM `Artists` AS `a`
-WHERE `a`.`Name` IN ('Back\\slasher''s', 'John''s Chill Box')");
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+ """
+ SELECT `a`.`ArtistId`, `a`.`Name`
+ FROM `Artists` AS `a`
+ WHERE `a`.`Name` IN (
+ SELECT `a0`.`value`
+ FROM JSON_TABLE('["Back\\\\slasher\\u0027s","John\\u0027s Chill Box"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longtext PATH '$[0]'
+ )) AS `a0`
+ )
+ """);
+ }
+ else
+ {
+ AssertSql(
+ """
+ SELECT `a`.`ArtistId`, `a`.`Name`
+ FROM `Artists` AS `a`
+ WHERE `a`.`Name` IN ('Back\\slasher''s', 'John''s Chill Box')
+ """);
+ }
}
public class EscapesSingleStoreFixture : EscapesSingleStoreFixtureBase
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/FiltersInheritanceQuerySingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/Query/FiltersInheritanceQuerySingleStoreFixture.cs
deleted file mode 100644
index 1fde92ca5..000000000
--- a/test/EFCore.SingleStore.FunctionalTests/Query/FiltersInheritanceQuerySingleStoreFixture.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel;
-
-namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
-{
- public class FiltersInheritanceQuerySingleStoreFixture : InheritanceQuerySingleStoreFixture
- {
- protected override bool EnableFilters => true;
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- base.OnModelCreating(modelBuilder, context);
-
- // We're changing the data type of the fields from INT to BIGINT, because in SingleStore
- // on a sharded (distributed) table, AUTO_INCREMENT can only be used on a BIGINT column
- modelBuilder.Entity()
- .Property(e => e.Id)
- .HasColumnType("bigint");
-
- modelBuilder.Entity()
- .Property(e => e.Id)
- .HasColumnType("bigint");
-
- modelBuilder.Entity()
- .Property(e => e.Id)
- .HasColumnType("bigint");
- }
- }
-}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/FiltersInheritanceQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/FiltersInheritanceQuerySingleStoreTest.cs
deleted file mode 100644
index c03192e32..000000000
--- a/test/EFCore.SingleStore.FunctionalTests/Query/FiltersInheritanceQuerySingleStoreTest.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Linq;
-using System.Threading.Tasks;
-using EntityFrameworkCore.SingleStore.Tests;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
-{
- public class FiltersInheritanceQuerySingleStoreTest : FiltersInheritanceQueryTestBase
- {
- public FiltersInheritanceQuerySingleStoreTest(FiltersInheritanceQuerySingleStoreFixture fixture, ITestOutputHelper testOutputHelper)
- : base(fixture)
- {
- Fixture.TestSqlLoggerFactory.Clear();
- //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
- }
-
-
- [ConditionalTheory]
- public override async Task Can_use_IgnoreQueryFilters_and_GetDatabaseValues(bool async)
- {
- // We're skipping this test when we're running tests on Managed Service due to the specifics of
- // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
- if (AppConfig.ManagedService)
- {
- return;
- }
-
- await base.Can_use_IgnoreQueryFilters_and_GetDatabaseValues(async);
- }
- }
-}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs
index 2724c29a8..391f09868 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs
@@ -1,33 +1,192 @@
-using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using System.Threading.Tasks;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
+namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query;
+
+public class FunkyDataQuerySingleStoreTest : FunkyDataQueryTestBase
{
- public class FunkyDataQuerySingleStoreTest : FunkyDataQueryTestBase
+ public FunkyDataQuerySingleStoreTest(FunkyDataQuerySingleStoreFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ public override async Task String_contains_on_argument_with_wildcard_constant(bool async)
+ {
+ await base.String_contains_on_argument_with_wildcard_constant(async);
+
+ AssertSql(
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '%\\%B%'
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '%a\\_%'
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE FALSE
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` IS NOT NULL
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '%\\_Ba\\_%'
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` NOT LIKE '%\\%B\\%a\\%r%' OR (`f`.`FirstName` IS NULL)
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` IS NULL
+""",
+ //
+ """
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+""");
+ }
+
+ public override async Task String_starts_with_on_argument_with_wildcard_constant(bool async)
{
- public FunkyDataQuerySingleStoreTest(FunkyDataQuerySingleStoreFixture fixture)
- : base(fixture)
- {
- ClearLog();
- //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
- }
-
- protected override void ClearLog()
- => Fixture.TestSqlLoggerFactory.Clear();
-
- private void AssertSql(params string[] expected)
- => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
-
- public class FunkyDataQuerySingleStoreFixture : FunkyDataQueryFixtureBase
- {
- public TestSqlLoggerFactory TestSqlLoggerFactory
- => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService();
-
- protected override ITestStoreFactory TestStoreFactory
- => SingleStoreTestStoreFactory.Instance;
- }
+ await base.String_starts_with_on_argument_with_wildcard_constant(async);
+
+ AssertSql(
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '\\%B%'
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '\\_B%'
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE FALSE
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` IS NOT NULL
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '\\_Ba\\_%'
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` NOT LIKE '\\%B\\%a\\%r%' OR (`f`.`FirstName` IS NULL)
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` IS NULL
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+""");
+ }
+
+ public override async Task String_ends_with_on_argument_with_wildcard_constant(bool async)
+ {
+ await base.String_ends_with_on_argument_with_wildcard_constant(async);
+
+ AssertSql(
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '%\\%r'
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '%r\\_'
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE FALSE
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` IS NOT NULL
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` LIKE '%\\_r\\_'
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` NOT LIKE '%a\\%r\\%' OR (`f`.`FirstName` IS NULL)
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+WHERE `f`.`FirstName` IS NULL
+""",
+ //
+"""
+SELECT `f`.`FirstName`
+FROM `FunkyCustomers` AS `f`
+""");
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ public class FunkyDataQuerySingleStoreFixture : FunkyDataQueryFixtureBase
+ {
+ public TestSqlLoggerFactory TestSqlLoggerFactory
+ => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService();
+
+ protected override ITestStoreFactory TestStoreFactory
+ => SingleStoreTestStoreFactory.Instance;
}
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.cs
index 5e915a58a..35df4d23d 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.cs
@@ -231,7 +231,7 @@ public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullab
return base.Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Conditional_expression_with_test_being_simplified_to_constant_complex(bool isAsync)
{
return base.Conditional_expression_with_test_being_simplified_to_constant_complex(isAsync);
@@ -249,19 +249,19 @@ public override Task Correlated_collection_with_very_complex_order_by(bool async
return base.Correlated_collection_with_very_complex_order_by(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Correlated_collections_with_FirstOrDefault(bool async)
{
return base.Correlated_collections_with_FirstOrDefault(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(bool async)
{
return base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task FirstOrDefault_over_int_compared_to_zero(bool async)
{
return base.FirstOrDefault_over_int_compared_to_zero(async);
@@ -291,61 +291,61 @@ public override Task Include_with_complex_order_by(bool async)
return base.Include_with_complex_order_by(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Multiple_orderby_with_navigation_expansion_on_one_of_the_order_bys_inside_subquery_complex_orderings(bool async)
{
return base.Multiple_orderby_with_navigation_expansion_on_one_of_the_order_bys_inside_subquery_complex_orderings(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Optional_navigation_with_collection_composite_key(bool async)
{
return base.Optional_navigation_with_collection_composite_key(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Project_one_value_type_converted_to_nullable_from_empty_collection(bool async)
{
return base.Project_one_value_type_converted_to_nullable_from_empty_collection(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Project_one_value_type_from_empty_collection(bool async)
{
return base.Project_one_value_type_from_empty_collection(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Query_with_complex_let_containing_ordering_and_filter_projecting_firstOrDefault_element_of_let(bool async)
{
return base.Query_with_complex_let_containing_ordering_and_filter_projecting_firstOrDefault_element_of_let(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Select_subquery_boolean(bool async)
{
return base.Select_subquery_boolean(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Select_subquery_boolean_with_pushdown(bool async)
{
return base.Select_subquery_boolean_with_pushdown(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Select_subquery_int_with_inside_cast_and_coalesce(bool async)
{
return base.Select_subquery_int_with_inside_cast_and_coalesce(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Select_subquery_int_with_outside_cast_and_coalesce(bool async)
{
return base.Select_subquery_int_with_outside_cast_and_coalesce(async);
}
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed")]
public override Task Select_subquery_int_with_pushdown_and_coalesce(bool async)
{
return base.Select_subquery_int_with_pushdown_and_coalesce(async);
@@ -440,5 +440,128 @@ public override Task Correlated_collection_with_groupby_with_complex_grouping_ke
{
return base.Correlated_collection_with_groupby_with_complex_grouping_key_not_projecting_identifier_column_with_group_aggregate_in_final_projection(async);
}
+
+ [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore Distributed.")]
+ public override Task Where_subquery_with_ElementAtOrDefault_equality_to_null_with_composite_key(bool async)
+ {
+ return base.Where_subquery_with_ElementAtOrDefault_equality_to_null_with_composite_key(async);
+ }
+
+ public override async Task Group_by_on_StartsWith_with_null_parameter_as_argument(bool async)
+ {
+ await base.Group_by_on_StartsWith_with_null_parameter_as_argument(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Key`
+FROM (
+ SELECT FALSE AS `Key`
+ FROM `Gears` AS `g`
+) AS `t`
+GROUP BY `t`.`Key`
+""");
+ }
+
+ public override async Task Array_access_on_byte_array(bool async)
+ {
+ await base.Array_access_on_byte_array(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`
+FROM `Squads` AS `s`
+WHERE ASCII(SUBSTRING(`s`.`Banner5`, 2 + 1, 1)) = 6
+""");
+ }
+
+ public override async Task DateTimeOffset_to_unix_time_milliseconds(bool async)
+ {
+ await base.DateTimeOffset_to_unix_time_milliseconds(async);
+
+ AssertSql(
+"""
+@__unixEpochMilliseconds_0='0'
+
+SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`Discriminator`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`, `s1`.`SquadId`, `s1`.`MissionId`
+FROM `Gears` AS `g`
+INNER JOIN `Squads` AS `s` ON `g`.`SquadId` = `s`.`Id`
+LEFT JOIN `SquadMissions` AS `s1` ON `s`.`Id` = `s1`.`SquadId`
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM `SquadMissions` AS `s0`
+ INNER JOIN `Missions` AS `m` ON `s0`.`MissionId` = `m`.`Id`
+ WHERE (`s`.`Id` = `s0`.`SquadId`) AND (@__unixEpochMilliseconds_0 = (TIMESTAMPDIFF(microsecond, '1970-01-01 00:00:00', `m`.`Timeline`)) DIV (1000)))
+ORDER BY `g`.`Nickname`, `g`.`SquadId`, `s`.`Id`, `s1`.`SquadId`
+""");
+ }
+
+ public override async Task DateTimeOffset_to_unix_time_seconds(bool async)
+ {
+ await base.DateTimeOffset_to_unix_time_seconds(async);
+
+ AssertSql(
+"""
+@__unixEpochSeconds_0='0'
+
+SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`Discriminator`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`, `s1`.`SquadId`, `s1`.`MissionId`
+FROM `Gears` AS `g`
+INNER JOIN `Squads` AS `s` ON `g`.`SquadId` = `s`.`Id`
+LEFT JOIN `SquadMissions` AS `s1` ON `s`.`Id` = `s1`.`SquadId`
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM `SquadMissions` AS `s0`
+ INNER JOIN `Missions` AS `m` ON `s0`.`MissionId` = `m`.`Id`
+ WHERE (`s`.`Id` = `s0`.`SquadId`) AND (@__unixEpochSeconds_0 = TIMESTAMPDIFF(second, '1970-01-01 00:00:00', `m`.`Timeline`)))
+ORDER BY `g`.`Nickname`, `g`.`SquadId`, `s`.`Id`, `s1`.`SquadId`
+""");
+ }
+
+ public override async Task Group_by_with_having_StartsWith_with_null_parameter_as_argument(bool async)
+ {
+ await base.Group_by_with_having_StartsWith_with_null_parameter_as_argument(async);
+
+ AssertSql(
+"""
+SELECT `g`.`FullName`
+FROM `Gears` AS `g`
+GROUP BY `g`.`FullName`
+HAVING FALSE
+""");
+ }
+
+ public override async Task Select_StartsWith_with_null_parameter_as_argument(bool async)
+ {
+ await base.Select_StartsWith_with_null_parameter_as_argument(async);
+
+ AssertSql(
+"""
+SELECT FALSE
+FROM `Gears` AS `g`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.LimitWithNonConstantValue))]
+ public override async Task Where_subquery_with_ElementAt_using_column_as_index(bool async)
+ {
+ await base.Where_subquery_with_ElementAt_using_column_as_index(async);
+
+ AssertSql("");
+ }
+
+ public override async Task Where_datetimeoffset_hour_component(bool async)
+ {
+ await AssertQuery(
+ async,
+ ss => from m in ss.Set()
+ where m.Timeline.Hour == /* 10 */ 8
+ select m);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(hour FROM `m`.`Timeline`) = 8
+""");
+ }
}
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/InheritanceQuerySingleStoreFixture.cs b/test/EFCore.SingleStore.FunctionalTests/Query/InheritanceQuerySingleStoreFixture.cs
deleted file mode 100644
index c52b24981..000000000
--- a/test/EFCore.SingleStore.FunctionalTests/Query/InheritanceQuerySingleStoreFixture.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
-using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel;
-using Microsoft.EntityFrameworkCore.TestUtilities;
-
-namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
-{
- public class InheritanceQuerySingleStoreFixture : InheritanceQueryRelationalFixture
- {
- protected override ITestStoreFactory TestStoreFactory
- => SingleStoreTestStoreFactory.Instance;
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- base.OnModelCreating(modelBuilder, context);
-
- // We're changing the data type of the fields from INT to BIGINT, because in SingleStore
- // on a sharded (distributed) table, AUTO_INCREMENT can only be used on a BIGINT column
- modelBuilder.Entity()
- .Property(e => e.Id)
- .HasColumnType("bigint");
-
- modelBuilder.Entity()
- .Property(e => e.Id)
- .HasColumnType("bigint");
-
- modelBuilder.Entity()
- .Property(e => e.Id)
- .HasColumnType("bigint");
-
-#pragma warning disable CS0618 // Type or member is obsolete
- modelBuilder.Entity()
- .HasNoKey()
- .ToQuery(
- () => context.Set()
- .FromSqlRaw(@"SELECT * FROM `Animals`"));
-#pragma warning restore CS0618 // Type or member is obsolete
- }
- }
-}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/InheritanceQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/InheritanceQuerySingleStoreTest.cs
deleted file mode 100644
index fb566fda9..000000000
--- a/test/EFCore.SingleStore.FunctionalTests/Query/InheritanceQuerySingleStoreTest.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System.Linq;
-using System.Threading.Tasks;
-using EntityFrameworkCore.SingleStore.Tests;
-using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
-{
- public class InheritanceQuerySingleStoreTest : InheritanceRelationalQueryTestBase
- {
- public InheritanceQuerySingleStoreTest(InheritanceQuerySingleStoreFixture fixture, ITestOutputHelper testOutputHelper)
- : base(fixture)
- {
- Fixture.TestSqlLoggerFactory.Clear();
- //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
- }
-
- [ConditionalTheory(Skip = "https://github.com/mysql-net/SingleStoreConnector/pull/896")]
- public override Task Byte_enum_value_constant_used_in_projection(bool async)
- {
- return base.Byte_enum_value_constant_used_in_projection(async);
- }
-
- [ConditionalFact(Skip = "Feature 'FOREIGN KEY' is not supported by SingleStore.")]
- public override void Setting_foreign_key_to_a_different_type_throws()
- {
- base.Setting_foreign_key_to_a_different_type_throws();
- }
-
- [ConditionalFact]
- public override void Can_insert_update_delete()
- {
- // We're skipping this test when we're running tests on Managed Service due to the specifics of
- // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
- if (AppConfig.ManagedService)
- {
- return;
- }
-
- base.Can_insert_update_delete();
- }
-
- [ConditionalTheory]
- public override Task Can_include_prey(bool async)
- {
- // We're skipping this test when we're running tests on Managed Service due to the specifics of
- // how AUTO_INCREMENT works (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-table/#auto-increment-behavior)
- if (AppConfig.ManagedService)
- {
- return Task.CompletedTask;
- }
-
- return base.Can_include_prey(async);
- }
- }
-}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/MatchQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/MatchQuerySingleStoreTest.cs
index 0f90e1a85..cf39042c3 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/MatchQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/MatchQuerySingleStoreTest.cs
@@ -21,169 +21,338 @@ public MatchQuerySingleStoreTest(MatchQuerySingleStoreFixture fixture)
public virtual void Match()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First") > 0);
Assert.Equal(3, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('First')");
+WHERE MATCH (`h`.`Name`) AGAINST ('First') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "First"));
+
+ Assert.Equal(3, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('First') > 0.0");
}
[ConditionalFact]
public virtual void Match_multiple_columns()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First"));
+ var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First") > 0);
Assert.Equal(5, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First')");
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_multiple_columns()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(new []{herb.Name, herb.Garden}, "First"));
+
+ Assert.Equal(5, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First') > 0.0");
}
[ConditionalFact]
public virtual void Match_keywords_separated()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First, Second"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First, Second") > 0);
+
+ Assert.Equal(6, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('First, Second') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_keywords_separated()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "First, Second"));
Assert.Equal(6, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('First, Second')");
+WHERE MATCH (`h`.`Name`) AGAINST ('First, Second') > 0.0");
}
[ConditionalFact]
public virtual void Match_keywords_separated_multiple_columns()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First, Second"));
+ var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First, Second") > 0);
Assert.Equal(8, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First, Second')");
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First, Second') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_keywords_separated_multiple_columns()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(new []{herb.Name, herb.Garden}, "First, Second"));
+
+ Assert.Equal(8, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First, Second') > 0.0");
}
[ConditionalFact]
public virtual void Match_multiple_keywords()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First Herb"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First Herb") > 0);
+
+ Assert.Equal(9, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('First Herb') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_multiple_keywords()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "First Herb"));
Assert.Equal(9, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('First Herb')");
+WHERE MATCH (`h`.`Name`) AGAINST ('First Herb') > 0.0");
}
[ConditionalFact]
public virtual void Match_multiple_keywords_multiple_columns()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First Herb"));
+ var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First Herb") > 0);
+
+ Assert.Equal(9, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First Herb') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_multiple_keywords_multiple_columns()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(new []{herb.Name, herb.Garden}, "First Herb"));
Assert.Equal(9, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First Herb')");
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First Herb') > 0.0");
}
[ConditionalFact]
public virtual void Match_multiple_keywords_separated()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First, Second"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First, Second") > 0);
+
+ Assert.Equal(6, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('First, Second') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_multiple_keywords_separated()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "First, Second"));
Assert.Equal(6, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('First, Second')");
+WHERE MATCH (`h`.`Name`) AGAINST ('First, Second') > 0.0");
}
[ConditionalFact]
public virtual void Match_multiple_keywords_separated_multiple_columns()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First, Second"));
+ var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First, Second") > 0);
Assert.Equal(8, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First, Second')");
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First, Second') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_multiple_keywords_separated_multiple_columns()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(new []{herb.Name, herb.Garden}, "First, Second"));
+
+ Assert.Equal(8, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First, Second') > 0.0");
}
[ConditionalFact]
public virtual void Match_with_wildcard()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First*"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First*") > 0);
Assert.Equal(3, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('First*')");
+WHERE MATCH (`h`.`Name`) AGAINST ('First*') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_with_wildcard()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "First*"));
+
+ Assert.Equal(3, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('First*') > 0.0");
}
[ConditionalFact]
public virtual void Match_in_boolean_mode_keywords()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First* Herb*"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "First* Herb*") > 0);
Assert.Equal(9, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('First* Herb*')");
+WHERE MATCH (`h`.`Name`) AGAINST ('First* Herb*') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_in_boolean_mode_keywords()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "First* Herb*"));
+
+ Assert.Equal(9, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('First* Herb*') > 0.0");
}
[ConditionalFact]
public virtual void Match_keywords_multiple_columns()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First* Herb*"));
+ var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "First* Herb*") > 0);
+
+ Assert.Equal(9, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First* Herb*') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_keywords_multiple_columns()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(new []{herb.Name, herb.Garden}, "First* Herb*"));
Assert.Equal(9, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First* Herb*')");
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('First* Herb*') > 0.0");
}
[ConditionalFact]
public virtual void Match_keyword_excluded()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "Herb* -Second"));
+ var count = context.Set().Count(herb => EF.Functions.Match(herb.Name, "Herb* -Second") > 0);
+
+ Assert.Equal(6, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`) AGAINST ('Herb* -Second') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_keyword_excluded()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(herb.Name, "Herb* -Second"));
Assert.Equal(6, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`) AGAINST ('Herb* -Second')");
+WHERE MATCH (`h`.`Name`) AGAINST ('Herb* -Second') > 0.0");
}
[ConditionalFact]
public virtual void Match_keyword_excluded_multiple_columns()
{
using var context = CreateContext();
- var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "Herb* -Second"));
+ var count = context.Set().Count(herb => EF.Functions.Match(new []{herb.Name, herb.Garden}, "Herb* -Second") > 0);
+
+ Assert.Equal(4, count);
+
+ AssertSql(@"SELECT COUNT(*)
+FROM `Herb` AS `h`
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('Herb* -Second') > 0.0");
+ }
+
+ [ConditionalFact]
+ public virtual void IsMatch_keyword_excluded_multiple_columns()
+ {
+ using var context = CreateContext();
+ var count = context.Set().Count(herb => EF.Functions.IsMatch(new []{herb.Name, herb.Garden}, "Herb* -Second"));
Assert.Equal(4, count);
AssertSql(@"SELECT COUNT(*)
FROM `Herb` AS `h`
-WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('Herb* -Second')");
+WHERE MATCH (`h`.`Name`, `h`.`Garden`) AGAINST ('Herb* -Second') > 0.0");
}
private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySingleStoreTest.cs
new file mode 100644
index 000000000..0ee7e9200
--- /dev/null
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySingleStoreTest.cs
@@ -0,0 +1,503 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using EntityFrameworkCore.SingleStore.Tests;
+using Xunit;
+
+namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query;
+
+public class NonSharedPrimitiveCollectionsQuerySingleStoreTest : NonSharedPrimitiveCollectionsQueryRelationalTestBase
+{
+ #region Support for specific element types
+
+ public override async Task Array_of_string()
+ {
+ await base.Array_of_string();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longtext PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 'a') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_int()
+ {
+ await base.Array_of_int();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` int PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 1) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_long()
+ {
+ await base.Array_of_long();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` bigint PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 1) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_short()
+ {
+ await base.Array_of_short();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` smallint PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 1) = 2
+LIMIT 2
+""");
+ }
+
+ [ConditionalFact]
+ public override Task Array_of_byte()
+ => base.Array_of_byte();
+
+ public override async Task Array_of_double()
+ {
+ await base.Array_of_double();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` double PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 1.0) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_float()
+ {
+ await base.Array_of_float();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` float PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 1) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_decimal()
+ {
+ await base.Array_of_decimal();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` decimal(65,30) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 1.0) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_DateTime()
+ {
+ await base.Array_of_DateTime();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` datetime(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIMESTAMP '2023-01-01 12:30:00') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_DateTime_with_milliseconds()
+ {
+ await base.Array_of_DateTime_with_milliseconds();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` datetime(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIMESTAMP '2023-01-01 12:30:00.123') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_DateTime_with_microseconds()
+ {
+ await base.Array_of_DateTime_with_microseconds();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` datetime(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIMESTAMP '2023-01-01 12:30:00.123456') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_DateOnly()
+ {
+ await base.Array_of_DateOnly();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` date PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = DATE '2023-01-01') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_TimeOnly()
+ {
+ await base.Array_of_TimeOnly();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` time(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIME '12:30:00') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_TimeOnly_with_milliseconds()
+ {
+ await base.Array_of_TimeOnly_with_milliseconds();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` time(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIME '12:30:00.123') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_TimeOnly_with_microseconds()
+ {
+ await base.Array_of_TimeOnly_with_microseconds();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` time(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIME '12:30:00.123456') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_DateTimeOffset()
+ {
+ await base.Array_of_DateTimeOffset();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` datetime(6) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = TIMESTAMP '2023-01-01 10:30:00') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_bool()
+ {
+ await base.Array_of_bool();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` tinyint(1) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value`) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_Guid()
+ {
+ await base.Array_of_Guid();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(36) PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 'dc8c903d-d655-4144-a0fd-358099d40ae1') = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_byte_array()
+ {
+ // This does not work, because the byte array is base64 encoded for some reason.
+ await base.Array_of_byte_array();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` longblob PATH '$[0]'
+ )) AS `s`
+ WHERE FROM_BASE64(`s`.`value`) = 0x0102) = 2
+LIMIT 2
+""");
+ }
+
+ public override async Task Array_of_enum()
+ {
+ await base.Array_of_enum();
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`SomeArray`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM JSON_TABLE(`t`.`SomeArray`, '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` int PATH '$[0]'
+ )) AS `s`
+ WHERE `s`.`value` = 0) = 2
+LIMIT 2
+""");
+ }
+
+ [ConditionalFact]
+ public override Task Array_of_array_is_not_supported()
+ => base.Array_of_array_is_not_supported();
+
+ [ConditionalFact]
+ public override Task Multidimensional_array_is_not_supported()
+ => base.Multidimensional_array_is_not_supported();
+
+ #endregion Support for specific element types
+
+ [ConditionalFact]
+ public override Task Column_with_custom_converter()
+ => base.Column_with_custom_converter();
+
+ public override async Task Parameter_with_inferred_value_converter()
+ {
+ await base.Parameter_with_inferred_value_converter();
+
+ AssertSql("");
+ }
+
+ #region Type mapping inference
+
+ public override async Task Constant_with_inferred_value_converter()
+ {
+ await base.Constant_with_inferred_value_converter();
+
+ if (AppConfig.ServerVersion.Supports.ValuesWithRows)
+ {
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`, `t`.`PropertyWithValueConverter`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM (SELECT CAST(1 AS signed) AS `Value` UNION ALL VALUES ROW(8)) AS `v`
+ WHERE `v`.`Value` = `t`.`PropertyWithValueConverter`) = 1
+LIMIT 2
+""");
+ }
+ else if (AppConfig.ServerVersion.Supports.Values)
+ {
+
+ }
+ else
+ {
+ AssertSql(
+ """
+ SELECT `t`.`Id`, `t`.`Ints`, `t`.`PropertyWithValueConverter`
+ FROM `TestEntity` AS `t`
+ WHERE (
+ SELECT COUNT(*)
+ FROM (SELECT CAST(1 AS signed) AS `Value` UNION ALL SELECT 8) AS `v`
+ WHERE `v`.`Value` = `t`.`PropertyWithValueConverter`) = 1
+ LIMIT 2
+ """);
+ }
+ }
+
+ public override async Task Inline_collection_in_query_filter()
+ {
+ await base.Inline_collection_in_query_filter();
+
+ if (AppConfig.ServerVersion.Supports.ValuesWithRows)
+ {
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM (SELECT CAST(1 AS signed) AS `Value` UNION ALL VALUES ROW(2), ROW(3)) AS `v`
+ WHERE `v`.`Value` > `t`.`Id`) = 1
+LIMIT 2
+""");
+ }
+ else if (AppConfig.ServerVersion.Supports.Values)
+ {
+ }
+ else
+ {
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Ints`
+FROM `TestEntity` AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM (SELECT CAST(1 AS signed) AS `Value` UNION ALL SELECT 2 UNION ALL SELECT 3) AS `v`
+ WHERE `v`.`Value` > `t`.`Id`) = 1
+LIMIT 2
+""");
+ }
+ }
+
+ public override async Task Column_collection_inside_json_owned_entity()
+ {
+ await base.Column_collection_inside_json_owned_entity();
+
+ AssertSql(
+ """
+SELECT TOP(2) [t].[Id], [t].[Owned]
+FROM [TestOwner] AS [t]
+WHERE (
+ SELECT COUNT(*)
+ FROM OPENJSON(JSON_VALUE([t].[Owned], '$.Strings')) AS [s]) = 2
+""",
+ //
+ """
+SELECT TOP(2) [t].[Id], [t].[Owned]
+FROM [TestOwner] AS [t]
+WHERE JSON_VALUE(JSON_VALUE([t].[Owned], '$.Strings'), '$[1]') = N'bar'
+""");
+ }
+
+ #endregion Type mapping inference
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ protected override ITestStoreFactory TestStoreFactory
+ => SingleStoreTestStoreFactory.Instance;
+}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs
index 7a4e34586..9a67ff55e 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using System.Linq;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
@@ -36,16 +37,34 @@ public override Task Average_over_nested_subquery_is_client_eval(bool async)
selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Average(od => od.ProductID)),
asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
+ // TODO: Implement TranslatePrimitiveCollection.
public override async Task Contains_with_local_anonymous_type_array_closure(bool async)
{
// Aggregates. Issue #15937.
- await AssertTranslationFailed(() => base.Contains_with_local_anonymous_type_array_closure(async));
+ // await AssertTranslationFailed(() => base.Contains_with_local_anonymous_type_array_closure(async));
+
+ await Assert.ThrowsAsync(() => base.Contains_with_local_anonymous_type_array_closure(async));
AssertSql();
}
+ // TODO: Implement TranslatePrimitiveCollection
public override async Task Contains_with_local_tuple_array_closure(bool async)
- => await AssertTranslationFailed(() => base.Contains_with_local_tuple_array_closure(async));
+ {
+ // await AssertTranslationFailed(() => base.Contains_with_local_tuple_array_closure(async));
+
+ await Assert.ThrowsAsync(() => base.Contains_with_local_tuple_array_closure(async));
+ }
+
+ public override async Task Contains_with_local_enumerable_inline(bool async)
+ {
+ // Issue #31776
+ await Assert.ThrowsAsync(
+ async () =>
+ await base.Contains_with_local_enumerable_inline(async));
+
+ AssertSql();
+ }
[ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
public override Task Collection_Last_member_access_in_projection_translated(bool async)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateQuerySingleStoreTest.cs
deleted file mode 100644
index df6b447fd..000000000
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateQuerySingleStoreTest.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.TestModels.Northwind;
-using Microsoft.EntityFrameworkCore.TestUtilities;
-using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
-{
- public class NorthwindAggregateQuerySingleStoreTest : NorthwindAggregateOperatorsQueryRelationalTestBase<
- NorthwindQuerySingleStoreFixture>
- {
- public NorthwindAggregateQuerySingleStoreTest(
- NorthwindQuerySingleStoreFixture fixture,
- ITestOutputHelper testOutputHelper)
- : base(fixture)
- {
- ClearLog();
- //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
- }
-
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
- public override Task Collection_Last_member_access_in_projection_translated(bool async)
- {
- return base.Collection_Last_member_access_in_projection_translated(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
- public override Task Collection_LastOrDefault_member_access_in_projection_translated(bool async)
- {
- return base.Collection_LastOrDefault_member_access_in_projection_translated(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
- public override Task First_inside_subquery_gets_client_evaluated(bool async)
- {
- return base.First_inside_subquery_gets_client_evaluated(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Correlated subselect that can not be transformed and does not match on shard keys' is not supported by SingleStore")]
- public override Task FirstOrDefault_inside_subquery_gets_server_evaluated(bool async)
- {
- return base.FirstOrDefault_inside_subquery_gets_server_evaluated(async);
- }
-
- [ConditionalTheory(Skip = "SingleStore does not support this type of query: scalar subselect references field belonging to outer select that is more than one level up")]
- public override Task Multiple_collection_navigation_with_FirstOrDefault_chained_projecting_scalar(bool async)
- {
- return base.Multiple_collection_navigation_with_FirstOrDefault_chained_projecting_scalar(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override async Task Average_over_max_subquery_is_client_eval(bool async)
- {
- await AssertAverage(
- async,
- ss => ss.Set().OrderBy(c => c.CustomerID).Take(3),
- selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Max(od => od.ProductID)),
- asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
-
- AssertSql(
- $@"@__p_0='3'
-SELECT AVG(CAST((
- SELECT AVG({SingleStoreTestHelpers.CastAsDouble(@"5 + (
- SELECT MAX(`o0`.`ProductID`)
- FROM `Order Details` AS `o0`
- WHERE `o`.`OrderID` = `o0`.`OrderID`)")})
- FROM `Orders` AS `o`
- WHERE `t`.`CustomerID` = `o`.`CustomerID`) AS decimal(65,30)))
-FROM (
- SELECT `c`.`CustomerID`
- FROM `Customers` AS `c`
- ORDER BY `c`.`CustomerID`
- LIMIT @__p_0
-) AS `t`");
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override async Task Average_over_nested_subquery_is_client_eval(bool async)
- {
- await AssertAverage(
- async,
- ss => ss.Set().OrderBy(c => c.CustomerID).Take(3),
- selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Average(od => od.ProductID)),
- asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
-
- AssertSql(
- $@"@__p_0='3'
-SELECT AVG(CAST((
- SELECT AVG(5.0 + (
- SELECT AVG({SingleStoreTestHelpers.CastAsDouble(@"`o0`.`ProductID`")})
- FROM `Order Details` AS `o0`
- WHERE `o`.`OrderID` = `o0`.`OrderID`))
- FROM `Orders` AS `o`
- WHERE `t`.`CustomerID` = `o`.`CustomerID`) AS decimal(65,30)))
-FROM (
- SELECT `c`.`CustomerID`
- FROM `Customers` AS `c`
- ORDER BY `c`.`CustomerID`
- LIMIT @__p_0
-) AS `t`");
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Average_over_subquery_is_client_eval(bool async)
- {
- return base.Average_over_max_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Max_over_nested_subquery_is_client_eval(bool async)
- {
- return base.Max_over_nested_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Max_over_subquery_is_client_eval(bool async)
- {
- return base.Max_over_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Max_over_sum_subquery_is_client_eval(bool async)
- {
- return base.Max_over_sum_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Min_over_max_subquery_is_client_eval(bool async)
- {
- return base.Min_over_max_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Min_over_nested_subquery_is_client_eval(bool async)
- {
- return base.Min_over_nested_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Min_over_subquery_is_client_eval(bool async)
- {
- return base.Min_over_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Sum_on_float_column_in_subquery(bool async)
- {
- return base.Sum_on_float_column_in_subquery(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Sum_over_min_subquery_is_client_eval(bool async)
- {
- return base.Sum_over_min_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Sum_over_nested_subquery_is_client_eval(bool async)
- {
- return base.Sum_over_nested_subquery_is_client_eval(async);
- }
-
- [ConditionalTheory(Skip = "Feature 'Subselect in aggregate functions' is not supported by SingleStore")]
- public override Task Sum_over_subquery_is_client_eval(bool async)
- {
- return base.Sum_over_subquery_is_client_eval(async);
- }
-
- public override async Task Contains_with_local_anonymous_type_array_closure(bool async)
- {
- // Aggregates. Issue #15937.
- await AssertTranslationFailed(() => base.Contains_with_local_anonymous_type_array_closure(async));
-
- AssertSql();
- }
-
- public override async Task Contains_with_local_tuple_array_closure(bool async)
- => await AssertTranslationFailed(() => base.Contains_with_local_tuple_array_closure(async));
-
- public override async Task Sum_with_coalesce(bool async)
- {
- await base.Sum_with_coalesce(async);
-
- AssertSql(
- @"SELECT COALESCE(SUM(COALESCE(`p`.`UnitPrice`, 0.0)), 0.0)
-FROM `Products` AS `p`
-WHERE `p`.`ProductID` < 40");
- }
-
- private void AssertSql(params string[] expected)
- => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
-
- protected override void ClearLog()
- => Fixture.TestSqlLoggerFactory.Clear();
- }
-}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindChangeTrackingQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindChangeTrackingQuerySingleStoreTest.cs
index ee49c3c79..53b98b506 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindChangeTrackingQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindChangeTrackingQuerySingleStoreTest.cs
@@ -3,6 +3,7 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestModels.Northwind;
using Xunit;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query
@@ -47,7 +48,7 @@ public override void Multiple_entities_can_revert()
}
protected override NorthwindContext CreateNoTrackingContext()
- => new NorthwindRelationalContext(
+ => new NorthwindSingleStoreContext(
new DbContextOptionsBuilder(Fixture.CreateOptions())
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking).Options);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindCompiledQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindCompiledQuerySingleStoreTest.cs
index d18dfd049..85c1032ae 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindCompiledQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindCompiledQuerySingleStoreTest.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
@@ -23,68 +22,6 @@ public NorthwindCompiledQuerySingleStoreTest(
//fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- [ConditionalFact]
- public override void Query_with_array_parameter()
- {
- var query = EF.CompileQuery(
- (NorthwindContext context, string[] args)
- => context.Customers.Where(c => c.CustomerID == args[0]));
-
- using (var context = CreateContext())
- {
- // We still throw an InvalidOperationException, which is expected, but with a different error message,
- // because of the implemented array support for JSON.
- Assert.Throws(
- () => query(context, new[] {"ALFKI"})
- .First()
- .CustomerID);
- }
-
- using (var context = CreateContext())
- {
- // We still throw an InvalidOperationException, which is expected, but with a different error message,
- // because of the implemented array support for JSON.
- Assert.Throws(
- () => query(context, new[] {"ANATR"})
- .First()
- .CustomerID);
- }
- }
-
- [ConditionalFact]
- public override async Task Query_with_array_parameter_async()
- {
- var query = EF.CompileAsyncQuery(
- (NorthwindContext context, string[] args)
- => context.Customers.Where(c => c.CustomerID == args[0]));
-
- using (var context = CreateContext())
- {
- // We still throw an InvalidOperationException, which is expected, but with a different error message,
- // because of the implemented array support for JSON.
- await Assert.ThrowsAsync(
- () => query(context, new[] {"ALFKI"})
- .ToListAsync());
- }
-
- using (var context = CreateContext())
- {
- // We still throw an InvalidOperationException, which is expected, but with a different error message,
- // because of the implemented array support for JSON.
- await Assert.ThrowsAsync(
- () => query(context, new[] {"ANATR"})
- .ToListAsync());
- }
- }
-
- public override void MakeBinary_does_not_throw_for_unsupported_operator()
- {
- Assert.Equal(
- CoreStrings.TranslationFailed("DbSet() .Where(c => c.CustomerID == (string)(__parameters[0]))"),
- Assert.Throws(
- () => base.MakeBinary_does_not_throw_for_unsupported_operator()).Message.Replace("\r", "").Replace("\n", ""));
- }
-
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindDbFunctionsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindDbFunctionsQuerySingleStoreTest.cs
index 80363c0d9..41cd66e4d 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindDbFunctionsQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindDbFunctionsQuerySingleStoreTest.cs
@@ -1,6 +1,11 @@
+using System;
+using System.Linq.Expressions;
using System.Threading.Tasks;
+using EntityFrameworkCore.SingleStore.Tests;
+using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Xunit;
using Xunit.Abstractions;
@@ -20,7 +25,25 @@ public NorthwindDbFunctionsQuerySingleStoreTest(
public override async Task Like_literal(bool async)
{
- await base.Like_literal(async);
+ Expression> expectedPredicate;
+ if (AppConfig.ServerVersion.Version.Major >= 9)
+ {
+ // starting from 9.0 SingleStore's standard collation is utf8mb4_bin
+ // it is case‑sensitive, so only uppercase M will match the LIKE pattern
+ expectedPredicate = c => c.ContactName.Contains("M");
+ }
+ else
+ {
+ // older collations are case‑insensitive
+ expectedPredicate = c => c.ContactName.Contains("M") || c.ContactName.Contains("m");
+ }
+
+ await AssertCount(
+ async,
+ ss => ss.Set(),
+ ss => ss.Set(),
+ c => EF.Functions.Like(c.ContactName, "%M%"),
+ expectedPredicate);
AssertSql(
@"SELECT COUNT(*)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs
index c65151532..b52dc5a60 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs
@@ -6,6 +6,9 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
+using EntityFrameworkCore.SingleStore.Tests;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using Xunit;
namespace EntityFrameworkCore.SingleStore.FunctionalTests.Query;
@@ -148,7 +151,7 @@ public override async Task Include_collection_alias_generation(bool async)
SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
FROM `Orders` AS `o`
LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
ORDER BY `o`.`OrderID`, `o0`.`OrderID`
""");
}
@@ -249,7 +252,7 @@ public override async Task Include_references_then_include_collection(bool async
FROM `Orders` AS `o`
LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
ORDER BY `o`.`OrderID`, `c`.`CustomerID`
""");
}
@@ -415,8 +418,7 @@ from c2 in ss.Set().OrderBy(c => c.CustomerID).Skip(2).Take(2)
{
AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
AssertEqual(e.c2, a.c2);
- },
- entryCount: 8);
+ });
AssertSql(
"""
@@ -459,8 +461,7 @@ await AssertQuery(
.OrderBy(b => b.Customer.CustomerID != null)
.ThenBy(b => b.Customer != null ? b.Customer.CustomerID : string.Empty)
.ThenBy(b => b.EmployeeID) // Needs to be explicitly ordered by EmployeeID as well
- .Take(2),
- entryCount: 6);
+ .Take(2));
AssertSql(
"""
@@ -662,21 +663,53 @@ public override async Task Include_collection_OrderBy_list_does_not_contains(boo
{
await base.Include_collection_OrderBy_list_does_not_contains(async);
- AssertSql(
-"""
-@__p_1='1'
-
-SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
-FROM (
- SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `c`.`CustomerID` <> 'ALFKI' AS `c`
- FROM `Customers` AS `c`
- WHERE `c`.`CustomerID` LIKE 'A%'
- ORDER BY `c`.`CustomerID` <> 'ALFKI'
- LIMIT 18446744073709551610 OFFSET @__p_1
-) AS `t`
-LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
-ORDER BY `t`.`c`, `t`.`CustomerID`
-""");
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, NOT (COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('["ALFKI"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE)) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY NOT (COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('["ALFKI"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE))
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
+ else
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `c`.`CustomerID` <> 'ALFKI' AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID` <> 'ALFKI'
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
}
public override async Task Include_reference_dependent_already_tracked(bool async)
@@ -1007,7 +1040,38 @@ public override async Task Include_collection_OrderBy_empty_list_contains(bool a
{
await base.Include_collection_OrderBy_empty_list_contains(async);
- AssertSql(
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('[]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('[]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE)
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
+ else
+ {
+ AssertSql(
"""
@__p_1='1'
@@ -1022,6 +1086,7 @@ LIMIT 18446744073709551610 OFFSET @__p_1
LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
ORDER BY `t`.`c`, `t`.`CustomerID`
""");
+ }
}
public override async Task Include_references_and_collection_multi_level(bool async)
@@ -1141,8 +1206,7 @@ await AssertQuery(
.ThenBy(o => o.Customer != null ? o.Customer.City : string.Empty)
.ThenBy(o => o.OrderID) // Needs to be explicitly ordered by EmployeeID as well
.Take(5),
- elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)),
- entryCount: 14);
+ elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)));
AssertSql(
"""
@@ -1176,7 +1240,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn
SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Orders` AS `o`
LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
""");
}
@@ -1322,21 +1386,6 @@ public override async Task Include_multiple_references_then_include_collection_m
""");
}
- public override async Task Outer_idenfier_correctly_determined_when_doing_include_on_right_side_of_left_join(bool async)
- {
- await base.Outer_idenfier_correctly_determined_when_doing_include_on_right_side_of_left_join(async);
-
- AssertSql(
-"""
-SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
-FROM `Customers` AS `c`
-LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
-LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
-WHERE `c`.`City` = 'Seattle'
-ORDER BY `c`.`CustomerID`, `o`.`OrderID`, `o0`.`OrderID`
-""");
- }
-
public override async Task SelectMany_Include_reference_GroupBy_Select(bool async)
{
await base.SelectMany_Include_reference_GroupBy_Select(async);
@@ -1399,21 +1448,53 @@ public override async Task Include_collection_OrderBy_list_contains(bool async)
{
await base.Include_collection_OrderBy_list_contains(async);
- AssertSql(
-"""
-@__p_1='1'
-
-SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
-FROM (
- SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `c`.`CustomerID` = 'ALFKI' AS `c`
- FROM `Customers` AS `c`
- WHERE `c`.`CustomerID` LIKE 'A%'
- ORDER BY `c`.`CustomerID` = 'ALFKI'
- LIMIT 18446744073709551610 OFFSET @__p_1
-) AS `t`
-LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
-ORDER BY `t`.`c`, `t`.`CustomerID`
-""");
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('["ALFKI"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('["ALFKI"]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE)
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
+ else
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `c`.`CustomerID` = 'ALFKI' AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID` = 'ALFKI'
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
}
public override async Task Multi_level_includes_are_applied_with_skip(bool async)
@@ -1526,8 +1607,7 @@ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).
{
AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
AssertInclude(e.c2, a.c2, new ExpectedInclude(c => c.Orders));
- },
- entryCount: 15);
+ });
AssertSql(
"""
@@ -1571,7 +1651,7 @@ public override async Task Include_reference(bool async)
SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Orders` AS `o`
LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
""");
}
@@ -1801,7 +1881,7 @@ public override async Task Include_collection_and_reference(bool async)
FROM `Orders` AS `o`
LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
ORDER BY `o`.`OrderID`, `c`.`CustomerID`, `o0`.`OrderID`
""");
}
@@ -1889,21 +1969,53 @@ public override async Task Include_collection_OrderBy_empty_list_does_not_contai
{
await base.Include_collection_OrderBy_empty_list_does_not_contains(async);
- AssertSql(
-"""
-@__p_1='1'
-
-SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
-FROM (
- SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, TRUE AS `c`
- FROM `Customers` AS `c`
- WHERE `c`.`CustomerID` LIKE 'A%'
- ORDER BY (SELECT 1)
- LIMIT 18446744073709551610 OFFSET @__p_1
-) AS `t`
-LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
-ORDER BY `t`.`c`, `t`.`CustomerID`
-""");
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, NOT (COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('[]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE)) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY NOT (COALESCE(`c`.`CustomerID` IN (
+ SELECT `l`.`value`
+ FROM JSON_TABLE('[]', '$[*]' COLUMNS (
+ `key` FOR ORDINALITY,
+ `value` char(5) PATH '$[0]'
+ )) AS `l`
+ ), FALSE))
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
+ else
+ {
+ AssertSql(
+ """
+ @__p_1='1'
+
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, TRUE AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY (SELECT 1)
+ LIMIT 18446744073709551610 OFFSET @__p_1
+ ) AS `t`
+ LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `t`.`c`, `t`.`CustomerID`
+ """);
+ }
}
public override async Task Include_multiple_references_then_include_multi_level_reverse(bool async)
@@ -1931,7 +2043,7 @@ public override async Task Include_reference_and_collection(bool async)
FROM `Orders` AS `o`
LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
ORDER BY `o`.`OrderID`, `c`.`CustomerID`, `o0`.`OrderID`
""");
}
@@ -2003,7 +2115,7 @@ public override async Task Include_reference_and_collection_order_by(bool async)
FROM `Orders` AS `o`
LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
-WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+WHERE `o`.`CustomerID` LIKE 'F%'
ORDER BY `o`.`OrderID`, `c`.`CustomerID`
""");
}
@@ -2303,6 +2415,21 @@ public override async Task Include_collection_with_client_filter(bool async)
AssertSql();
}
+ public override async Task Outer_identifier_correctly_determined_when_doing_include_on_right_side_of_left_join(bool async)
+ {
+ await base.Outer_identifier_correctly_determined_when_doing_include_on_right_side_of_left_join(async);
+
+ AssertSql(
+ """
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Customers` AS `c`
+ LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+ LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ WHERE `c`.`City` = 'Seattle'
+ ORDER BY `c`.`CustomerID`, `o`.`OrderID`, `o0`.`OrderID`
+ """);
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.SingleStore.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.SingleStore.cs
index 47423ff91..b74bd2f2e 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.SingleStore.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.SingleStore.cs
@@ -14,9 +14,7 @@ public async Task PadLeft_without_second_arg(bool async)
{
await AssertQuery(
async,
- ss => ss.Set().Where(r => r.CustomerID.PadLeft(8) == " ALFKI"),
- entryCount: 1);
-
+ ss => ss.Set().Where(r => r.CustomerID.PadLeft(8) == " ALFKI"));
AssertSql(
@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
@@ -29,8 +27,7 @@ public async Task PadLeft_with_second_arg(bool async)
{
await AssertQuery(
async,
- ss => ss.Set().Where(r => r.CustomerID.PadLeft(8, 'x') == "xxxALFKI"),
- entryCount: 1);
+ ss => ss.Set().Where(r => r.CustomerID.PadLeft(8, 'x') == "xxxALFKI"));
AssertSql(
@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
@@ -44,8 +41,7 @@ public async Task PadRight_without_second_arg(bool async)
{
await AssertQuery(
async,
- ss => ss.Set().Where(r => r.CustomerID.PadRight(8) == "ALFKI "),
- entryCount: 1);
+ ss => ss.Set().Where(r => r.CustomerID.PadRight(8) == "ALFKI "));
AssertSql(
@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
@@ -59,8 +55,7 @@ public async Task PadRight_with_second_arg(bool async)
{
await AssertQuery(
async,
- ss => ss.Set().Where(r => r.CustomerID.PadRight(8, 'c') == "ALFKIccc"),
- entryCount: 1);
+ ss => ss.Set().Where(r => r.CustomerID.PadRight(8, 'c') == "ALFKIccc"));
AssertSql(
@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs
index 87adecf46..65616342a 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs
@@ -4,7 +4,6 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
-using EntityFrameworkCore.SingleStore.Tests;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Xunit;
using Xunit.Abstractions;
@@ -32,9 +31,11 @@ public override async Task String_StartsWith_Literal(bool async)
await base.String_StartsWith_Literal(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`ContactName` IS NOT NULL AND (`c`.`ContactName` LIKE 'M%')");
+WHERE `c`.`ContactName` LIKE 'M%'
+""");
}
[ConditionalTheory]
@@ -43,9 +44,11 @@ public override async Task String_StartsWith_Identity(bool async)
await base.String_StartsWith_Identity(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (`c`.`ContactName` = '') OR (`c`.`ContactName` IS NOT NULL AND (LEFT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`))");
+WHERE `c`.`ContactName` IS NOT NULL AND (LEFT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`)
+""");
}
[ConditionalTheory]
@@ -54,9 +57,11 @@ public override async Task String_StartsWith_Column(bool async)
await base.String_StartsWith_Column(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (`c`.`ContactName` = '') OR (`c`.`ContactName` IS NOT NULL AND (LEFT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`))");
+WHERE `c`.`ContactName` IS NOT NULL AND (LEFT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`)
+""");
}
[ConditionalTheory]
@@ -65,9 +70,11 @@ public override async Task String_StartsWith_MethodCall(bool async)
await base.String_StartsWith_MethodCall(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`ContactName` IS NOT NULL AND (`c`.`ContactName` LIKE 'M%')");
+WHERE `c`.`ContactName` LIKE 'M%'
+""");
}
[ConditionalTheory]
@@ -76,9 +83,11 @@ public override async Task String_EndsWith_Literal(bool async)
await base.String_EndsWith_Literal(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`ContactName` IS NOT NULL AND (`c`.`ContactName` LIKE '%b')");
+WHERE `c`.`ContactName` LIKE '%b'
+""");
}
[ConditionalTheory]
@@ -87,9 +96,11 @@ public override async Task String_EndsWith_Identity(bool async)
await base.String_EndsWith_Identity(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (`c`.`ContactName` = '') OR (`c`.`ContactName` IS NOT NULL AND (RIGHT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`))");
+WHERE `c`.`ContactName` IS NOT NULL AND (RIGHT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`)
+""");
}
[ConditionalTheory]
@@ -98,9 +109,11 @@ public override async Task String_EndsWith_Column(bool async)
await base.String_EndsWith_Column(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (`c`.`ContactName` = '') OR (`c`.`ContactName` IS NOT NULL AND (RIGHT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`))");
+WHERE `c`.`ContactName` IS NOT NULL AND (RIGHT(`c`.`ContactName`, CHAR_LENGTH(`c`.`ContactName`)) = `c`.`ContactName`)
+""");
}
[ConditionalTheory]
@@ -109,9 +122,11 @@ public override async Task String_EndsWith_MethodCall(bool async)
await base.String_EndsWith_MethodCall(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`ContactName` IS NOT NULL AND (`c`.`ContactName` LIKE '%m')");
+WHERE `c`.`ContactName` LIKE '%m'
+""");
}
[ConditionalTheory(Skip = "SingleStore LIKE operator is case insensitive")]
@@ -120,9 +135,11 @@ public override async Task String_Contains_Literal(bool async)
await base.String_Contains_Literal(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`ContactName` LIKE '%M%'");
+WHERE `c`.`ContactName` LIKE '%M%'
+""");
}
[ConditionalTheory]
@@ -131,9 +148,11 @@ public override async Task String_Contains_Identity(bool async)
await base.String_Contains_Identity(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (`c`.`ContactName` LIKE '') OR (LOCATE(`c`.`ContactName`, `c`.`ContactName`) > 0)");
+WHERE `c`.`ContactName` IS NOT NULL AND ((LOCATE(`c`.`ContactName`, `c`.`ContactName`) > 0) OR (`c`.`ContactName` LIKE ''))
+""");
}
[ConditionalTheory]
@@ -142,9 +161,11 @@ public override async Task String_Contains_Column(bool async)
await base.String_Contains_Column(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (`c`.`ContactName` LIKE '') OR (LOCATE(`c`.`ContactName`, `c`.`ContactName`) > 0)");
+WHERE `c`.`ContactName` IS NOT NULL AND ((LOCATE(`c`.`ContactName`, `c`.`ContactName`) > 0) OR (`c`.`ContactName` LIKE ''))
+""");
}
[ConditionalTheory(Skip = "SingleStore LIKE operator is case insensitive")]
@@ -333,9 +354,11 @@ public override async Task Where_math_abs_uncorrelated(bool async)
await base.Where_math_abs_uncorrelated(async);
AssertSql(
- @"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`UnitPrice` < 7.0) AND (10 < `o`.`ProductID`)");
+WHERE (`o`.`UnitPrice` < 7.0) AND (10 < `o`.`ProductID`)
+""");
}
[ConditionalTheory]
@@ -511,11 +534,13 @@ public override async Task String_Contains_parameter_with_whitespace(bool async)
await base.String_Contains_parameter_with_whitespace(async);
AssertSql(
- $@"@__pattern_0=' ' (Size = 4000)
+"""
+@__pattern_0_rewritten='% %' (Size = 30)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE (@__pattern_0 LIKE '') OR (LOCATE(@__pattern_0, `c`.`ContactName`) > 0)");
+WHERE `c`.`ContactName` LIKE @__pattern_0_rewritten
+""");
}
public override async Task String_LastOrDefault_MethodCall(bool async)
@@ -889,29 +914,41 @@ public override async Task String_Compare_simple_zero(bool async)
await base.String_Compare_simple_zero(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` = 'ALFKI'",
+WHERE `c`.`CustomerID` = 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <> 'ALFKI'",
+WHERE `c`.`CustomerID` <> 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > 'ALFKI'",
+WHERE `c`.`CustomerID` > 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'",
+WHERE `c`.`CustomerID` <= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > 'ALFKI'",
+WHERE `c`.`CustomerID` > 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'");
+WHERE `c`.`CustomerID` <= 'AROUT'
+""");
}
public override async Task String_Compare_simple_one(bool async)
@@ -919,29 +956,41 @@ public override async Task String_Compare_simple_one(bool async)
await base.String_Compare_simple_one(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > 'ALFKI'",
+WHERE `c`.`CustomerID` > 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` < 'ALFKI'",
+WHERE `c`.`CustomerID` < 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'",
+WHERE `c`.`CustomerID` <= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'",
+WHERE `c`.`CustomerID` <= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= 'ALFKI'",
+WHERE `c`.`CustomerID` >= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= 'ALFKI'");
+WHERE `c`.`CustomerID` >= 'AROUT'
+""");
}
public override async Task String_compare_with_parameter(bool async)
@@ -949,41 +998,53 @@ public override async Task String_compare_with_parameter(bool async)
await base.String_compare_with_parameter(async);
AssertSql(
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` > @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` < @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` < @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` <= @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` <= @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` >= @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= @__customer_CustomerID_0");
+WHERE `c`.`CustomerID` >= @__customer_CustomerID_0
+""");
}
public override async Task String_Compare_simple_more_than_one(bool async)
@@ -1065,29 +1126,41 @@ public override async Task String_Compare_to_simple_zero(bool async)
await base.String_Compare_to_simple_zero(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` = 'ALFKI'",
+WHERE `c`.`CustomerID` = 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <> 'ALFKI'",
+WHERE `c`.`CustomerID` <> 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > 'ALFKI'",
+WHERE `c`.`CustomerID` > 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'",
+WHERE `c`.`CustomerID` <= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > 'ALFKI'",
+WHERE `c`.`CustomerID` > 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'");
+WHERE `c`.`CustomerID` <= 'AROUT'
+""");
}
public override async Task String_Compare_to_simple_one(bool async)
@@ -1095,29 +1168,41 @@ public override async Task String_Compare_to_simple_one(bool async)
await base.String_Compare_to_simple_one(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > 'ALFKI'",
+WHERE `c`.`CustomerID` > 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` < 'ALFKI'",
+WHERE `c`.`CustomerID` < 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'",
+WHERE `c`.`CustomerID` <= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= 'ALFKI'",
+WHERE `c`.`CustomerID` <= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= 'ALFKI'",
+WHERE `c`.`CustomerID` >= 'AROUT'
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= 'ALFKI'");
+WHERE `c`.`CustomerID` >= 'AROUT'
+""");
}
public override async Task String_compare_to_with_parameter(bool async)
@@ -1125,41 +1210,53 @@ public override async Task String_compare_to_with_parameter(bool async)
await base.String_compare_to_with_parameter(async);
AssertSql(
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` > @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` < @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` < @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` <= @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` <= @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= @__customer_CustomerID_0",
+WHERE `c`.`CustomerID` >= @__customer_CustomerID_0
+""",
//
- $@"@__customer_CustomerID_0='ALFKI' (Size = {SingleStoreTestHelpers.Instance.GetIndexedStringPropertyDefaultLength})
+"""
+@__customer_CustomerID_0='AROUT' (Size = 5) (DbType = StringFixedLength)
SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` >= @__customer_CustomerID_0");
+WHERE `c`.`CustomerID` >= @__customer_CustomerID_0
+""");
}
public override async Task String_Compare_to_simple_more_than_one(bool async)
@@ -1197,29 +1294,41 @@ public override async Task String_Compare_to_nested(bool async)
await base.String_Compare_to_nested(async);
AssertSql(
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` = (CONCAT('M', `c`.`CustomerID`))",
+WHERE `c`.`CustomerID` <> (CONCAT('M', `c`.`CustomerID`))
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <> UPPER(`c`.`CustomerID`)",
+WHERE `c`.`CustomerID` = UPPER(`c`.`CustomerID`)
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > REPLACE('ALFKI', 'ALF', `c`.`CustomerID`)",
+WHERE `c`.`CustomerID` > REPLACE('AROUT', 'OUT', `c`.`CustomerID`)
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` <= (CONCAT('M', `c`.`CustomerID`))",
+WHERE `c`.`CustomerID` <= (CONCAT('M', `c`.`CustomerID`))
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` > UPPER(`c`.`CustomerID`)",
+WHERE `c`.`CustomerID` > UPPER(`c`.`CustomerID`)
+""",
//
- $@"SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
FROM `Customers` AS `c`
-WHERE `c`.`CustomerID` < REPLACE('ALFKI', 'ALF', `c`.`CustomerID`)");
+WHERE `c`.`CustomerID` < REPLACE('AROUT', 'OUT', `c`.`CustomerID`)
+""");
}
public override async Task String_Compare_to_multi_predicate(bool async)
@@ -1696,6 +1805,607 @@ public override async Task Convert_ToString(bool async)
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
WHERE (`o`.`CustomerID` = 'ALFKI') AND ((CAST(`o`.`OrderDate` AS char) LIKE '%1997%') OR (CAST(`o`.`OrderDate` AS char) LIKE '%1998%'))");
+ }
+
+ public override async Task String_StartsWith_Parameter(bool async)
+ {
+ await base.String_StartsWith_Parameter(async);
+
+ AssertSql(
+"""
+@__pattern_0_rewritten='M%' (Size = 30)
+
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE `c`.`ContactName` LIKE @__pattern_0_rewritten
+""");
+ }
+
+ public override async Task String_EndsWith_Parameter(bool async)
+ {
+ await base.String_EndsWith_Parameter(async);
+
+ AssertSql(
+"""
+@__pattern_0_rewritten='%b' (Size = 30)
+
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE `c`.`ContactName` LIKE @__pattern_0_rewritten
+""");
+ }
+
+ public override async Task String_Join_over_non_nullable_column(bool async)
+ {
+ await base.String_Join_over_non_nullable_column(async);
+
+ AssertSql(
+"""
+SELECT `t`.`City`, `c0`.`CustomerID`
+FROM (
+ SELECT `c`.`City`
+ FROM `Customers` AS `c`
+ GROUP BY `c`.`City`
+) AS `t`
+LEFT JOIN `Customers` AS `c0` ON `t`.`City` = `c0`.`City`
+ORDER BY `t`.`City`
+""");
+ }
+
+ public override async Task String_Join_with_predicate(bool async)
+ {
+ await base.String_Join_with_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`City`, `t0`.`CustomerID`
+FROM (
+ SELECT `c`.`City`
+ FROM `Customers` AS `c`
+ GROUP BY `c`.`City`
+) AS `t`
+LEFT JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`City`
+ FROM `Customers` AS `c0`
+ WHERE CHAR_LENGTH(`c0`.`ContactName`) > 10
+) AS `t0` ON `t`.`City` = `t0`.`City`
+ORDER BY `t`.`City`
+""");
+ }
+
+ public override async Task String_Join_with_ordering(bool async)
+ {
+ await base.String_Join_with_ordering(async);
+
+ AssertSql(
+"""
+SELECT `t`.`City`, `c0`.`CustomerID`
+FROM (
+ SELECT `c`.`City`
+ FROM `Customers` AS `c`
+ GROUP BY `c`.`City`
+) AS `t`
+LEFT JOIN `Customers` AS `c0` ON `t`.`City` = `c0`.`City`
+ORDER BY `t`.`City`, `c0`.`CustomerID` DESC
+""");
+ }
+
+ public override async Task String_Join_over_nullable_column(bool async)
+ {
+ await base.String_Join_over_nullable_column(async);
+
+ AssertSql(
+"""
+SELECT `t`.`City`, `c0`.`Region`, `c0`.`CustomerID`
+FROM (
+ SELECT `c`.`City`
+ FROM `Customers` AS `c`
+ GROUP BY `c`.`City`
+) AS `t`
+LEFT JOIN `Customers` AS `c0` ON `t`.`City` = `c0`.`City`
+ORDER BY `t`.`City`
+""");
+ }
+
+ public override async Task String_Concat(bool async)
+ {
+ await base.String_Concat(async);
+
+ AssertSql(
+"""
+SELECT `t`.`City`, `c0`.`CustomerID`
+FROM (
+ SELECT `c`.`City`
+ FROM `Customers` AS `c`
+ GROUP BY `c`.`City`
+) AS `t`
+LEFT JOIN `Customers` AS `c0` ON `t`.`City` = `c0`.`City`
+ORDER BY `t`.`City`
+""");
+ }
+
+ public override async Task Where_math_square(bool async)
+ {
+ await base.Where_math_square(async);
+
+ AssertSql(
+$"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE POWER({SingleStoreTestHelpers.CastAsDouble("`o`.`Discount`")}, 2.0) > 0.05000000074505806
+""");
+ }
+
+ public override async Task Sum_over_round_works_correctly_in_projection(bool async)
+ {
+ await base.Sum_over_round_works_correctly_in_projection(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, (
+ SELECT COALESCE(SUM(ROUND(`o0`.`UnitPrice`, 2)), 0.0)
+ FROM `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = `o0`.`OrderID`) AS `Sum`
+FROM `Orders` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Sum_over_round_works_correctly_in_projection_2(bool async)
+ {
+ await base.Sum_over_round_works_correctly_in_projection_2(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, (
+ SELECT COALESCE(SUM(ROUND(`o0`.`UnitPrice` * `o0`.`UnitPrice`, 2)), 0.0)
+ FROM `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = `o0`.`OrderID`) AS `Sum`
+FROM `Orders` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Sum_over_truncate_works_correctly_in_projection(bool async)
+ {
+ await base.Sum_over_truncate_works_correctly_in_projection(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, (
+ SELECT COALESCE(SUM(TRUNCATE(`o0`.`UnitPrice`, 0)), 0.0)
+ FROM `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = `o0`.`OrderID`) AS `Sum`
+FROM `Orders` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Sum_over_truncate_works_correctly_in_projection_2(bool async)
+ {
+ await base.Sum_over_truncate_works_correctly_in_projection_2(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, (
+ SELECT COALESCE(SUM(TRUNCATE(`o0`.`UnitPrice` * `o0`.`UnitPrice`, 0)), 0.0)
+ FROM `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = `o0`.`OrderID`) AS `Sum`
+FROM `Orders` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Where_math_degrees(bool async)
+ {
+ await base.Where_math_degrees(async);
+
+ AssertSql(
+$"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (DEGREES({SingleStoreTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)
+""");
+ }
+
+ public override async Task Where_math_radians(bool async)
+ {
+ await base.Where_math_radians(async);
+
+ AssertSql(
+$"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (RADIANS({SingleStoreTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)
+""");
+ }
+
+ public override async Task Where_mathf_abs1(bool async)
+ {
+ await base.Where_mathf_abs1(async);
+
+ AssertSql(
+$"""
+SELECT `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Products` AS `p`
+WHERE ABS({SingleStoreTestHelpers.CastAsDouble("`p`.`ProductID`")}) > 10
+""");
+ }
+
+ public override async Task Where_mathf_ceiling1(bool async)
+ {
+ await base.Where_mathf_ceiling1(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`UnitPrice` < 7.0) AND (CEILING(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_floor(bool async)
+ {
+ await base.Where_mathf_floor(async);
+
+ AssertSql(
+$"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`Quantity` < 5) AND (FLOOR({SingleStoreTestHelpers.CastAsDouble("`o`.`UnitPrice`")}) > 10)
+""");
+ }
+
+ public override async Task Where_mathf_power(bool async)
+ {
+ await base.Where_mathf_power(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE POWER(`o`.`Discount`, 3) > 0.005
+""");
+ }
+
+ public override async Task Where_mathf_square(bool async)
+ {
+ await base.Where_mathf_square(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE POWER(`o`.`Discount`, 2) > 0.05
+""");
+ }
+
+ public override async Task Where_mathf_round2(bool async)
+ {
+ await base.Where_mathf_round2(async);
+
+ AssertSql(
+$"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE ROUND({SingleStoreTestHelpers.CastAsDouble("`o`.`UnitPrice`")}, 2) > 100
+""");
+ }
+
+ public override async Task Select_mathf_round(bool async)
+ {
+ await base.Select_mathf_round(async);
+
+ AssertSql(
+$"""
+SELECT ROUND({SingleStoreTestHelpers.CastAsDouble("`o`.`OrderID`")})
+FROM `Orders` AS `o`
+WHERE `o`.`OrderID` < 10250
+""");
+ }
+
+ public override async Task Select_mathf_round2(bool async)
+ {
+ await base.Select_mathf_round2(async);
+
+ AssertSql(
+$"""
+SELECT ROUND({SingleStoreTestHelpers.CastAsDouble("`o`.`UnitPrice`")}, 2)
+FROM `Order Details` AS `o`
+WHERE `o`.`Quantity` < 5
+""");
+ }
+
+ public override async Task Where_mathf_truncate(bool async)
+ {
+ await base.Where_mathf_truncate(async);
+
+ AssertSql(
+$"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`Quantity` < 5) AND (TRUNCATE({SingleStoreTestHelpers.CastAsDouble("`o`.`UnitPrice`")}, 0) > 10)
+""");
+ }
+
+ public override async Task Select_mathf_truncate(bool async)
+ {
+ await base.Select_mathf_truncate(async);
+
+ AssertSql(
+$"""
+SELECT TRUNCATE({SingleStoreTestHelpers.CastAsDouble("`o`.`UnitPrice`")}, 0)
+FROM `Order Details` AS `o`
+WHERE `o`.`Quantity` < 5
+""");
+ }
+
+ public override async Task Where_mathf_exp(bool async)
+ {
+ await base.Where_mathf_exp(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (EXP(`o`.`Discount`) > 1)
+""");
+ }
+
+ public override async Task Where_mathf_log10(bool async)
+ {
+ await base.Where_mathf_log10(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG10(`o`.`Discount`) < 0)
+""");
+ }
+
+ public override async Task Where_mathf_log(bool async)
+ {
+ await base.Where_mathf_log(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG(`o`.`Discount`) < 0)
+""");
+ }
+
+ public override async Task Where_mathf_log_new_base(bool async)
+ {
+ await base.Where_mathf_log_new_base(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG(`o`.`Discount`, 7) < 0)
+""");
+ }
+
+ public override async Task Where_mathf_sqrt(bool async)
+ {
+ await base.Where_mathf_sqrt(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (SQRT(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_acos(bool async)
+ {
+ await base.Where_mathf_acos(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (ACOS(`o`.`Discount`) > 1)
+""");
+ }
+
+ public override async Task Where_mathf_asin(bool async)
+ {
+ await base.Where_mathf_asin(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (ASIN(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_atan(bool async)
+ {
+ await base.Where_mathf_atan(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (ATAN(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_atan2(bool async)
+ {
+ await base.Where_mathf_atan2(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (ATAN2(`o`.`Discount`, 1) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_cos(bool async)
+ {
+ await base.Where_mathf_cos(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (COS(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_sin(bool async)
+ {
+ await base.Where_mathf_sin(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (SIN(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_tan(bool async)
+ {
+ await base.Where_mathf_tan(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (TAN(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_sign(bool async)
+ {
+ await base.Where_mathf_sign(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (SIGN(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_degrees(bool async)
+ {
+ await base.Where_mathf_degrees(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (DEGREES(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Where_mathf_radians(bool async)
+ {
+ await base.Where_mathf_radians(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
+FROM `Order Details` AS `o`
+WHERE (`o`.`OrderID` = 11077) AND (RADIANS(`o`.`Discount`) > 0)
+""");
+ }
+
+ public override async Task Indexof_with_one_constant_arg(bool async)
+ {
+ await base.Indexof_with_one_constant_arg(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE (LOCATE('a', `c`.`ContactName`) - 1) = 1
+""");
+ }
+
+ public override async Task Indexof_with_one_parameter_arg(bool async)
+ {
+ await base.Indexof_with_one_parameter_arg(async);
+
+ AssertSql(
+"""
+@__pattern_0='a' (Size = 4000)
+
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE (LOCATE(@__pattern_0, `c`.`ContactName`) - 1) = 1
+""");
+ }
+
+ public override async Task Indexof_with_constant_starting_position(bool async)
+ {
+ await base.Indexof_with_constant_starting_position(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE (LOCATE('a', `c`.`ContactName`, 3) - 1) = 4
+""");
+ }
+
+ public override async Task Indexof_with_parameter_starting_position(bool async)
+ {
+ await base.Indexof_with_parameter_starting_position(async);
+
+ AssertSql(
+"""
+@__start_0='2'
+
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE (LOCATE('a', `c`.`ContactName`, @__start_0 + 1) - 1) = 4
+""");
+ }
+
+ public override async Task Replace_using_property_arguments(bool async)
+ {
+ await base.Replace_using_property_arguments(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE REPLACE(`c`.`ContactName`, `c`.`ContactName`, `c`.`CustomerID`) = `c`.`CustomerID`
+""");
+ }
+
+ public override async Task IsNullOrEmpty_negated_in_predicate(bool async)
+ {
+ await base.IsNullOrEmpty_negated_in_predicate(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Customers` AS `c`
+WHERE `c`.`Region` IS NOT NULL AND (`c`.`Region` <> '')
+""");
+ }
+
+ public override async Task Where_DateOnly_FromDateTime(bool async)
+ {
+ await base.Where_DateOnly_FromDateTime(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Orders` AS `o`
+WHERE `o`.`OrderDate` IS NOT NULL AND (DATE(`o`.`OrderDate`) = '1996-09-16')
+""");
}
public override Task Datetime_subtraction_TotalDays(bool async)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindGroupByQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindGroupByQuerySingleStoreTest.cs
index 5fddce1b8..ab910d96b 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindGroupByQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindGroupByQuerySingleStoreTest.cs
@@ -186,14 +186,14 @@ public override Task GroupBy_Count_in_projection(bool async)
[SupportedServerVersionCondition("8.0.22-mysql", "0.0.0-mariadb")]
public override Task GroupBy_group_Where_Select_Distinct_aggregate(bool async)
{
- // See https://github.com/mysql-net/SingleStoreConnector/issues/898.
+ // See https://github.com/mysql-net/MySqlConnector/issues/898.
return base.GroupBy_group_Where_Select_Distinct_aggregate(async);
}
[SupportedServerVersionCondition("8.0.0-mysql", "0.0.0-mariadb")] // Is an issue issue in MySQL 5.7.34, but not in 8.0.25.
public override Task GroupBy_constant_with_where_on_grouping_with_aggregate_operators(bool async)
{
- // See https://github.com/mysql-net/SingleStoreConnector/issues/980.
+ // See https://github.com/mysql-net/MySqlConnector/issues/980.
return base.GroupBy_constant_with_where_on_grouping_with_aggregate_operators(async);
}
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySingleStoreTest.cs
index 194f43646..b867c219a 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySingleStoreTest.cs
@@ -41,8 +41,7 @@ from c2 in ss.Set().OrderBy(c => c.CustomerID).Skip(2).Take(2)
{
AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
AssertEqual(e.c2, a.c2);
- },
- entryCount: 8);
+ });
}
[ConditionalTheory(Skip = "SingleStore does not support this type of query: correlated subselect in ORDER BY")]
@@ -96,8 +95,7 @@ public override Task Repro9735(bool async)
.OrderBy(b => b.Customer.CustomerID != null)
.ThenBy(b => b.Customer != null ? b.Customer.CustomerID : string.Empty)
.ThenBy(b => b.EmployeeID) // Needs to be explicitly ordered by EmployeeID as well
- .Take(2),
- entryCount: 6);
+ .Take(2));
}
[ConditionalTheory(Skip = "SingleStore does not support this type of query: correlated subselect in ORDER BY")]
@@ -116,8 +114,7 @@ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).
{
AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
AssertInclude(e.c2, a.c2, new ExpectedInclude(c => c.Orders));
- },
- entryCount: 15);
+ });
}
[ConditionalTheory(Skip = "Feature 'scalar subselect inside the GROUP/ORDER BY of a pushed down query' is not supported by SingleStore")]
@@ -142,8 +139,7 @@ public override Task Include_collection_with_multiple_conditional_order_by(bool
.ThenBy(o => o.Customer != null ? o.Customer.City : string.Empty)
.ThenBy(b => b.EmployeeID) // Needs to be explicitly ordered by EmployeeID as well
.Take(5),
- elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)),
- entryCount: 14);
+ elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)));
}
private void AssertSql(params string[] expected)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeQuerySingleStoreTest.cs
index 19f1ffab3..931a23422 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindIncludeQuerySingleStoreTest.cs
@@ -43,8 +43,7 @@ public override Task Include_collection_with_multiple_conditional_order_by(bool
.ThenBy(o => o.Customer != null ? o.Customer.City : string.Empty)
.ThenBy(o => o.OrderID)
.Take(5),
- elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)),
- entryCount: 14);
+ elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)));
}
[ConditionalTheory(Skip = "SingleStore does not support this type of query: correlated subselect in ORDER BY")]
@@ -62,8 +61,7 @@ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).
{
AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
AssertInclude(e.c2, a.c2, new ExpectedInclude(c => c.Orders));
- },
- entryCount: 15);
+ });
}
[ConditionalTheory(Skip = "SingleStore does not support this type of query: correlated subselect in ORDER BY")]
@@ -81,8 +79,7 @@ from c2 in ss.Set().OrderBy(c => c.CustomerID).Skip(2).Take(2)
{
AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
AssertEqual(e.c2, a.c2);
- },
- entryCount: 8);
+ });
}
[ConditionalTheory(Skip = "SingleStore does not support this type of query: correlated subselect in ORDER BY")]
@@ -127,13 +124,13 @@ public override Task Then_include_collection_order_by_collection_column(bool asy
return base.Then_include_collection_order_by_collection_column(async);
}
- [ConditionalTheory(Skip = "Feature 'scalar subselect inside the GROUP/ORDER BY of a pushed down query' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'scalar subselect inside the GROUP/ORDER BY of a pushed down query' is not supported by SingleStore Distributed")]
public override Task Include_collection_OrderBy_empty_list_contains(bool async)
{
return base.Include_collection_OrderBy_empty_list_contains(async);
}
- [ConditionalTheory(Skip = "Feature 'scalar subselect inside the GROUP/ORDER BY of a pushed down query' is not supported by SingleStore")]
+ [ConditionalTheory(Skip = "Feature 'scalar subselect inside the GROUP/ORDER BY of a pushed down query' is not supported by SingleStore Distributed")]
public override Task Include_collection_OrderBy_empty_list_does_not_contains(bool async)
{
return base.Include_collection_OrderBy_empty_list_contains(async);
@@ -148,8 +145,7 @@ public override Task Repro9735(bool async)
.OrderBy(b => b.Customer.CustomerID != null)
.ThenBy(b => b.Customer != null ? b.Customer.CustomerID : string.Empty)
.ThenBy(b => b.EmployeeID) // Needs to be explicitly ordered by EmployeeID as well
- .Take(2),
- entryCount: 6);
+ .Take(2));
}
private void AssertSql(params string[] expected)
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindJoinQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindJoinQuerySingleStoreTest.cs
index be16e0666..75fbaaf99 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindJoinQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindJoinQuerySingleStoreTest.cs
@@ -1,6 +1,8 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
@@ -58,6 +60,22 @@ INNER JOIN (
ORDER BY `c`.`CustomerID`, `t0`.`OrderID0`, `t0`.`OrderID`");
}
+ // https://github.com/npgsql/efcore.pg/issues/2759
+ // public override Task Join_local_collection_int_closure_is_cached_correctly(bool async)
+ // => Assert.ThrowsAsync(() => base.Join_local_collection_int_closure_is_cached_correctly(async));
+ public override async Task Join_local_collection_int_closure_is_cached_correctly(bool async)
+ {
+ if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture))
+ {
+ await base.Join_local_collection_int_closure_is_cached_correctly(async);
+ }
+ else
+ {
+ await Assert.ThrowsAsync(()
+ => base.Join_local_collection_int_closure_is_cached_correctly(async));
+ }
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs
index 1bd0ac2b0..f0e781070 100644
--- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs
+++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs
@@ -206,7 +206,6 @@ public override Task Entity_equality_orderby_subquery(bool async)
async,
ss => ss.Set().OrderBy(c => c.Orders.OrderBy(o => o.OrderID).FirstOrDefault()).ThenBy(c => c.CustomerID),
ss => ss.Set().OrderBy(c => c.Orders.FirstOrDefault() == null ? (int?)null : c.Orders.OrderBy(o => o.OrderID).FirstOrDefault().OrderID).ThenBy(c => c.CustomerID),
- entryCount: 91,
assertOrder: true);
}
@@ -237,7 +236,6 @@ public override Task Checked_context_with_arithmetic_does_not_fail(bool isAsync)
ss => ss.Set