Skip to content
Open
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-tests-on-emulator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-tests-on-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ public List<Singers> SelectMultipleSingersEF()
public List<Singers> SelectMultipleSingersInReadOnlyTransactionSpanner()
{
using var connection = CreateConnection();
using var transaction = connection.BeginReadOnlyTransaction();
using var transaction = connection.BeginTransaction(SpannerTransactionCreationOptions.ReadOnly, new SpannerTransactionOptions());
using var command = connection.CreateSelectCommand("SELECT * FROM Singers ORDER BY LastName");
command.Transaction = transaction;
using var reader = command.ExecuteReader();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>

<IsPackable>false</IsPackable>

Expand All @@ -19,8 +19,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.17">
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ public async Task CanUseStringFormat()

var formattedName = await db.Singers
.Where(s => new long[] { singerId }.Contains(s.SingerId))
.Select(s => string.Format("String without formatting"))
.Select(s => string.Format("String without formatting", Array.Empty<object>()))
.FirstOrDefaultAsync();
Assert.Equal("String without formatting", formattedName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task ShouldGenerateOneTable()
var cmd = connection.CreateSelectCommand(
"SELECT COUNT(*) " +
"FROM INFORMATION_SCHEMA.TABLES " +
"WHERE TABLE_CATALOG='' AND TABLE_SCHEMA='' AND TABLE_NAME != 'EFMigrationsHistory'");
"WHERE TABLE_CATALOG='' AND TABLE_SCHEMA='' AND TABLE_NAME NOT IN('EFMigrationsHistory', 'EFMigrationsLock')");

using var reader = await cmd.ExecuteReaderAsync();
Assert.True(await reader.ReadAsync());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>SampleRunner</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand All @@ -24,7 +24,7 @@

<ItemGroup>
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.17">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>

<IsPackable>false</IsPackable>

Expand All @@ -18,11 +18,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.17">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="Xunit.Combinatorial" Version="1.6.24" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using Xunit;
using V1 = Google.Cloud.Spanner.V1;

namespace Google.Cloud.EntityFrameworkCore.Spanner.Tests.MigrationTests
{
Expand All @@ -43,6 +44,15 @@ public void TestMigrateUsesDdlBatch()
var version = typeof(Migration).Assembly.GetName().Version ?? new Version();
var formattedVersion = $"{version.Major}.{version.Minor}.{version.Build}";
_fixture.SpannerMock.AddOrUpdateStatementResult("SELECT 1", StatementResult.CreateException(MockSpannerService.CreateDatabaseNotFoundException("d1")));
_fixture.SpannerMock.AddOrUpdateStatementResult(
"SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_catalog = '' and table_schema = '' and table_name = '''EFMigrationsLock''')",
StatementResult.CreateSelect1ResultSet()
);
// Add mock result for the INSERT OR IGNORE migration lock statement - this has a dynamic timestamp so we'll use a pattern
_fixture.SpannerMock.AddOrUpdatePatternResult(
"INSERT OR IGNORE INTO `EFMigrationsLock`(`Id`, `Timestamp`) VALUES(1, '*')\nTHEN RETURN 1",
StatementResult.CreateSingleColumnResultSet(new V1.Type { Code = V1.TypeCode.Int64 }, "changes", 1L)
);
_fixture.SpannerMock.AddOrUpdateStatementResult(
$"INSERT INTO `EFMigrationsHistory` (`MigrationId`, `ProductVersion`)\nVALUES ('''20210309110233_Initial''', '''{formattedVersion}''')",
StatementResult.CreateUpdateCount(1)
Expand All @@ -51,6 +61,10 @@ public void TestMigrateUsesDdlBatch()
$"INSERT INTO `EFMigrationsHistory` (`MigrationId`, `ProductVersion`)\nVALUES ('''20210830_V2''', '''{formattedVersion}''')",
StatementResult.CreateUpdateCount(1)
);
_fixture.SpannerMock.AddOrUpdateStatementResult(
"DELETE FROM `EFMigrationsLock` WHERE 1 = 1;",
StatementResult.CreateUpdateCount(1)
);
using var db = new MockMigrationSampleDbContext(ConnectionString);
db.Database.Migrate();

Expand All @@ -62,7 +76,7 @@ public void TestMigrateUsesDdlBatch()
var update = request as UpdateDatabaseDdlRequest;
Assert.NotNull(update);
Assert.Collection(update.Statements,
sql => Assert.StartsWith("CREATE TABLE `EFMigrationsHistory`", sql)
sql => Assert.StartsWith("CREATE TABLE IF NOT EXISTS `EFMigrationsHistory`", sql)
);
},
// Each migration will be executed as a separate DDL batch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ public void Dispose()

private readonly object _lock = new();
private readonly ConcurrentDictionary<string, StatementResult> _results = new();
private readonly ConcurrentDictionary<string, StatementResult> _patternResults = new();
private ConcurrentQueue<IMessage> _requests = new();
private ConcurrentQueue<ServerCallContext> _contexts = new();
private ConcurrentQueue<Grpc.Core.Metadata> _headers = new ();
Expand All @@ -334,6 +335,74 @@ public void AddOrUpdateStatementResult(string sql, StatementResult result)
);
}

public void AddOrUpdatePatternResult(string sqlPattern, StatementResult result)
{
_patternResults.AddOrUpdate(sqlPattern.Trim(),
result,
(_, _) => result
);
}

private StatementResult FindStatementResult(string sql)
{
// First try exact match
if (_results.TryGetValue(sql.Trim(), out StatementResult result))
{
return result;
}

// Then try pattern matching
foreach (var pattern in _patternResults.Keys)
{
if (IsWildcardMatch(sql.Trim(), pattern))
{
return _patternResults[pattern];
}
}

return null;
}

private static bool IsWildcardMatch(string text, string pattern)
{
// Simple wildcard matching supporting * as wildcard
if (pattern == "*") return true;

var parts = pattern.Split('*');
if (parts.Length == 1)
{
// No wildcards, exact match
return text.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}

int currentIndex = 0;
for (int i = 0; i < parts.Length; i++)
{
var part = parts[i];
if (string.IsNullOrEmpty(part)) continue;

int foundIndex = text.IndexOf(part, currentIndex, StringComparison.OrdinalIgnoreCase);
if (foundIndex == -1) return false;

// First part must match from the beginning
if (i == 0 && foundIndex != 0) return false;

currentIndex = foundIndex + part.Length;
}

// Last part must match at the end (unless pattern ends with *)
if (!pattern.EndsWith("*") && parts.Length > 1)
{
var lastPart = parts[parts.Length - 1];
if (!string.IsNullOrEmpty(lastPart) && !text.EndsWith(lastPart, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}

return true;
}

public void AddOrUpdateExecutionTime(string method, ExecutionTime executionTime)
{
_executionTimes.AddOrUpdate(method,
Expand Down Expand Up @@ -370,6 +439,7 @@ public void Reset()
_headers = new ConcurrentQueue<Grpc.Core.Metadata>();
_executionTimes.Clear();
_results.Clear();
_patternResults.Clear();
_abortedTransactions.Clear();
_abortNextStatement = false;
}
Expand Down Expand Up @@ -592,7 +662,8 @@ public override Task<ExecuteBatchDmlResponse> ExecuteBatchDml(ExecuteBatchDmlReq
{
break;
}
if (_results.TryGetValue(statement.Sql.Trim(), out StatementResult result))
var result = FindStatementResult(statement.Sql);
if (result != null)
{
switch (result.Type)
{
Expand Down Expand Up @@ -652,7 +723,8 @@ public override async Task ExecuteStreamingSql(ExecuteSqlRequest request, IServe
{
returnTransaction = transaction;
}
if (_results.TryGetValue(request.Sql.Trim(), out StatementResult result))
var result = FindStatementResult(request.Sql);
if (result != null)
{
switch (result.Type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public static IServiceCollection AddEntityFrameworkSpanner(this IServiceCollecti
.TryAdd<IQuerySqlGeneratorFactory, SpannerQuerySqlGeneratorFactory>()
.TryAdd<IMethodCallTranslatorProvider, SpannerMethodCallTranslatorProvider>()
.TryAdd<IMemberTranslatorProvider, SpannerMemberTranslatorProvider>()
.TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, SpannerSqlTranslatingExpressionVisitorFactory>()
.TryAdd<IRelationalConnection>(p => p.GetService<ISpannerRelationalConnection>())
.TryAdd<IRelationalTransactionFactory, SpannerRelationalTransactionFactory>()
.TryAdd<IModelValidator, SpannerModelValidator>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>Google Cloud Spanner database provider for Entity Framework Core.</Description>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>Google.Cloud.EntityFrameworkCore.Spanner</AssemblyName>
<RootNamespace>Google.Cloud.EntityFrameworkCore.Spanner</RootNamespace>
<LangVersion>latest</LangVersion>
Expand All @@ -19,23 +19,23 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Cloud.Spanner.Data" Version="5.1.0"/>
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.17" PrivateAssets="All"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.17"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.17"/>
<PackageReference Include="OpenTelemetry.Api" Version="1.12.0"/>
<PackageReference Include="System.Text.Json" Version="8.0.5"/>
<PackageReference Include="Google.Cloud.Spanner.Data" Version="5.1.0" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" PrivateAssets="all" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
<PackageReference Include="OpenTelemetry.Api" Version="1.12.0" />
<PackageReference Include="System.Text.Json" Version="9.0.7" />
</ItemGroup>

<ItemGroup>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath/>
<PackagePath />
</None>
<None Include="..\NuGetIcon.png">
<Pack>True</Pack>
<PackagePath/>
<PackagePath />
</None>
</ItemGroup>

Expand Down
Loading
Loading