Skip to content

Commit

Permalink
Fall back to non-RETURNING updates with old Sqlite
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Aug 28, 2022
1 parent 54c6c99 commit 46a154d
Show file tree
Hide file tree
Showing 5 changed files with 512 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,29 @@ public static IServiceCollection AddEntityFrameworkSqlite(this IServiceCollectio
.TryAdd<IRelationalAnnotationProvider, SqliteAnnotationProvider>()
.TryAdd<IModelValidator, SqliteModelValidator>()
.TryAdd<IProviderConventionSetBuilder, SqliteConventionSetBuilder>()
.TryAdd<IUpdateSqlGenerator, SqliteUpdateSqlGenerator>()
.TryAdd<IModificationCommandBatchFactory, SqliteModificationCommandBatchFactory>()
.TryAdd<IRelationalConnection>(p => p.GetRequiredService<ISqliteRelationalConnection>())
.TryAdd<IMigrationsSqlGenerator, SqliteMigrationsSqlGenerator>()
.TryAdd<IRelationalDatabaseCreator, SqliteDatabaseCreator>()
.TryAdd<IHistoryRepository, SqliteHistoryRepository>()
.TryAdd<IRelationalQueryStringFactory, SqliteQueryStringFactory>()

// New Query Pipeline
.TryAdd<IMethodCallTranslatorProvider, SqliteMethodCallTranslatorProvider>()
.TryAdd<IAggregateMethodCallTranslatorProvider, SqliteAggregateMethodCallTranslatorProvider>()
.TryAdd<IMemberTranslatorProvider, SqliteMemberTranslatorProvider>()
.TryAdd<IQuerySqlGeneratorFactory, SqliteQuerySqlGeneratorFactory>()
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory, SqliteQueryableMethodTranslatingExpressionVisitorFactory>()
.TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, SqliteSqlTranslatingExpressionVisitorFactory>()
.TryAdd<IQueryTranslationPostprocessorFactory, SqliteQueryTranslationPostprocessorFactory>()
.TryAdd<IUpdateSqlGenerator>(sp =>
{
// Support for the RETURNING clause on INSERT/UPDATE/DELETE was added in Sqlite 3.35.
// Detect which version we're using, and fall back to the older INSERT/UPDATE+SELECT behavior on legacy versions.
var dependencies = sp.GetRequiredService<UpdateSqlGeneratorDependencies>();
return SQLitePCL.raw.sqlite3_libversion_number() < 3035000
? new SqliteLegacyUpdateSqlGenerator(dependencies)
: new SqliteUpdateSqlGenerator(dependencies);
})
.TryAddProviderSpecificServices(
b => b.TryAddScoped<ISqliteRelationalConnection, SqliteRelationalConnection>());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text;
using Microsoft.EntityFrameworkCore.Sqlite.Internal;

namespace Microsoft.EntityFrameworkCore.Sqlite.Update.Internal;

/// <summary>
/// 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.
/// </summary>
public class SqliteLegacyUpdateSqlGenerator : UpdateAndSelectSqlGenerator
{
/// <summary>
/// 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.
/// </summary>
public SqliteLegacyUpdateSqlGenerator(UpdateSqlGeneratorDependencies dependencies)
: base(dependencies)
{
}

/// <summary>
/// Appends a <c>WHERE</c> condition for the identity (i.e. key value) of the given column.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be appended.</param>
/// <param name="columnModification">The column for which the condition is being generated.</param>
protected override void AppendIdentityWhereCondition(StringBuilder commandStringBuilder, IColumnModification columnModification)
{
Check.NotNull(commandStringBuilder, nameof(commandStringBuilder));
Check.NotNull(columnModification, nameof(columnModification));

SqlGenerationHelper.DelimitIdentifier(commandStringBuilder, "rowid");
commandStringBuilder.Append(" = ")
.Append("last_insert_rowid()");
}

/// <summary>
/// Appends a SQL command for selecting the number of rows affected.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be appended.</param>
/// <param name="name">The name of the table.</param>
/// <param name="schema">The table schema, or <see langword="null" /> to use the default schema.</param>
/// <param name="commandPosition">The ordinal of the command for which rows affected it being returned.</param>
/// <returns>The <see cref="ResultSetMapping" /> for this command.</returns>
protected override ResultSetMapping AppendSelectAffectedCountCommand(StringBuilder commandStringBuilder, string name, string? schema, int commandPosition)
{
Check.NotNull(commandStringBuilder, nameof(commandStringBuilder));
Check.NotEmpty(name, nameof(name));

commandStringBuilder
.Append("SELECT changes()")
.AppendLine(SqlGenerationHelper.StatementTerminator)
.AppendLine();

return ResultSetMapping.LastInResultSet;
}

/// <summary>
/// Appends a <c>WHERE</c> condition checking rows affected.
/// </summary>
/// <param name="commandStringBuilder">The builder to which the SQL should be appended.</param>
/// <param name="expectedRowsAffected">The expected number of rows affected.</param>
protected override void AppendRowsAffectedWhereCondition(StringBuilder commandStringBuilder, int expectedRowsAffected)
{
Check.NotNull(commandStringBuilder, nameof(commandStringBuilder));

commandStringBuilder.Append("changes() = ").Append(expectedRowsAffected);
}

/// <summary>
/// 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.
/// </summary>
public override string GenerateNextSequenceValueOperation(string name, string? schema)
=> throw new NotSupportedException(SqliteStrings.SequencesNotSupported);
}
Loading

0 comments on commit 46a154d

Please sign in to comment.