Skip to content

Commit

Permalink
Implementation Hint: WITH LOCK
Browse files Browse the repository at this point in the history
*Start
  • Loading branch information
ralmsdeveloper committed Aug 9, 2018
1 parent 5790afa commit db46134
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 7 deletions.
1 change: 0 additions & 1 deletion EFCore.FirebirdSql.FunctionalTests/TestBasic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public void ReproIssue28()
var peoples = ctx
.People
.AsNoTracking()
.WithNoLock()
.Where(p => p.Id > 0)
.ToSql();

Expand Down
3 changes: 2 additions & 1 deletion EFCore.FirebirdSql.FunctionalTests/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

optionsBuilder
.UseFirebird(connectionString)
.ConfigureWarnings(c => c.Log(CoreEventId.IncludeIgnoredWarning));
.ConfigureWarnings(c => c.Log(CoreEventId.IncludeIgnoredWarning))
.EnableSensitiveDataLogging();

var loggerFactory = new LoggerFactory()
.AddConsole()
Expand Down
45 changes: 45 additions & 0 deletions EFCore.FirebirdSql.FunctionalTests/TestWithLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2017-2018 Rafael Almeida ([email protected])
*
* EntityFrameworkCore.FirebirdSql
*
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
*
* Permission is hereby granted to use or copy this program
* for any purpose, provided the above notices are retained on all copies.
* Permission to modify the code and to distribute modified code is granted,
* provided the above notices are retained, and a notice that the code was
* modified is included with the above copyright notice.
*
*/

using System.Linq;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace EFCore.FirebirdSql.FunctionalTests
{
public class TestWithLock
{
private TestContext CreateContext() => new TestContext();

[Fact]
public void Hint_with_lock()
{
using (var db = CreateContext())
{
var query = db
.Set<Author>()
.WithLock()
.Select(p=>new { p.AuthorId, p.Active})
.ToSql();

Assert.Equal(
query,
@"SELECT ""p"".""AuthorId"", ""p"".""Active""
FROM ""Author"" ""p"" WITH LOCK");
}
}
}
}
41 changes: 41 additions & 0 deletions EFCore.FirebirdSql/Extensions/FbQueryableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2017-2018 Rafael Almeida ([email protected])
*
* EntityFrameworkCore.FirebirdSql
*
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
*
* Permission is hereby granted to use or copy this program
* for any purpose, provided the above notices are retained on all copies.
* Permission to modify the code and to distribute modified code is granted,
* provided the above notices are retained, and a notice that the code was
* modified is included with the above copyright notice.
*
*/

using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Microsoft.EntityFrameworkCore
{
public static class FbQueryableExtensions
{
internal static readonly MethodInfo WithLockMethodInfo
= typeof(FbQueryableExtensions)
.GetTypeInfo().GetDeclaredMethods(nameof(WithLock))
.Single();

public static IQueryable<TEntity> WithLock<TEntity>(
this IQueryable<TEntity> source)
where TEntity : class
{
return source.Provider.CreateQuery<TEntity>(
Expression.Call(
null,
WithLockMethodInfo.MakeGenericMethod(typeof(TEntity)),
source.Expression));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators;
using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors;
using Microsoft.EntityFrameworkCore.Query.Sql;
Expand Down Expand Up @@ -63,6 +64,7 @@ public static IServiceCollection AddEntityFrameworkFirebird(this IServiceCollect
.TryAdd<IQuerySqlGeneratorFactory, FbQuerySqlGeneratorFactory>()
.TryAdd<ISingletonOptions, IFbOptions>(p => p.GetService<IFbOptions>())
.TryAdd<ISqlTranslatingExpressionVisitorFactory, FbSqlTranslatingExpressionVisitorFactory>()
.TryAdd<IQueryCompilationContextFactory, FbCompilationQueryableFactory>()
.TryAddProviderSpecificServices(b => b
.TryAddSingleton<IFbOptions, FbOptions>()
.TryAddScoped<IFbUpdateSqlGenerator, FbUpdateSqlGenerator>()
Expand Down
47 changes: 47 additions & 0 deletions EFCore.FirebirdSql/Query/FbCompilationContextFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2017-2018 Rafael Almeida ([email protected])
*
* EntityFrameworkCore.FirebirdSql
*
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
*
* Permission is hereby granted to use or copy this program
* for any purpose, provided the above notices are retained on all copies.
* Permission to modify the code and to distribute modified code is granted,
* provided the above notices are retained, and a notice that the code was
* modified is included with the above copyright notice.
*
*/

using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal;

namespace Microsoft.EntityFrameworkCore.Query
{
public class FbCompilationQueryableFactory : RelationalQueryCompilationContextFactory
{
public FbCompilationQueryableFactory(
QueryCompilationContextDependencies dependencies,
RelationalQueryCompilationContextDependencies relationalDependencies)
: base(dependencies,relationalDependencies)
{
relationalDependencies
.NodeTypeProviderFactory
.RegisterMethods(WithLockExpressionNode.SupportedMethods, typeof(WithLockExpressionNode));
}

public override QueryCompilationContext Create(bool async)
=> async
? new RelationalQueryCompilationContext(
Dependencies,
new AsyncLinqOperatorProvider(),
new AsyncQueryMethodProvider(),
TrackQueryResults)
: new RelationalQueryCompilationContext(
Dependencies,
new LinqOperatorProvider(),
new QueryMethodProvider(),
TrackQueryResults);
}
}
46 changes: 46 additions & 0 deletions EFCore.FirebirdSql/Query/Operators/WithLockExpressionNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2017-2018 Rafael Almeida ([email protected])
*
* EntityFrameworkCore.FirebirdSql
*
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
*
* Permission is hereby granted to use or copy this program
* for any purpose, provided the above notices are retained on all copies.
* Permission to modify the code and to distribute modified code is granted,
* provided the above notices are retained, and a notice that the code was
* modified is included with the above copyright notice.
*
*/

using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Remotion.Linq.Clauses;
using Remotion.Linq.Parsing.Structure.IntermediateModel;

namespace Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal
{
public class WithLockExpressionNode : ResultOperatorExpressionNodeBase
{
public static readonly IReadOnlyCollection<MethodInfo> SupportedMethods = new[]
{ FbQueryableExtensions.WithLockMethodInfo };

public WithLockExpressionNode(
MethodCallExpressionParseInfo parseInfo,
ConstantExpression WithLockExpressionExpression)
: base(parseInfo, null, null)
{
}

protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext)
=> new WithLockResultOperator();

public override Expression Resolve(
ParameterExpression inputParameter,
Expression expressionToBeResolved,
ClauseGenerationContext clauseGenerationContext)
=> Source.Resolve(inputParameter, expressionToBeResolved, clauseGenerationContext);
}
}
43 changes: 43 additions & 0 deletions EFCore.FirebirdSql/Query/Operators/WithLockResultOperator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2017-2018 Rafael Almeida ([email protected])
*
* EntityFrameworkCore.FirebirdSql
*
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
*
* Permission is hereby granted to use or copy this program
* for any purpose, provided the above notices are retained on all copies.
* Permission to modify the code and to distribute modified code is granted,
* provided the above notices are retained, and a notice that the code was
* modified is included with the above copyright notice.
*
*/

using System;
using System.Linq.Expressions;
using Remotion.Linq;
using Remotion.Linq.Clauses;
using Remotion.Linq.Clauses.ResultOperators;
using Remotion.Linq.Clauses.StreamedData;

namespace Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal
{
public class WithLockResultOperator : SequenceTypePreservingResultOperatorBase, IQueryAnnotation
{
public virtual IQuerySource QuerySource { get; set; }
public virtual QueryModel QueryModel { get; set; }
public virtual string Hint => ToString();
public override ResultOperatorBase Clone(CloneContext cloneContext)
=> new WithLockResultOperator();

public override void TransformExpressions(Func<Expression, Expression> transformation)
{
}

public override StreamedSequence ExecuteInMemory<T>(StreamedSequence input)
=> input;

public override string ToString() => " WITH LOCK";
}
}
60 changes: 55 additions & 5 deletions EFCore.FirebirdSql/Query/Sql/Internal/FbQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,84 @@
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using EntityFrameworkCore.FirebirdSql.Infrastructure.Internal;
using EntityFrameworkCore.FirebirdSql.Query.Expressions.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.ResultOperators;
using Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal;
using Microsoft.EntityFrameworkCore.Query.Sql;
using Microsoft.EntityFrameworkCore.Storage;

namespace EntityFrameworkCore.FirebirdSql.Query.Sql.Internal
{
public class FbQuerySqlGenerator : DefaultQuerySqlGenerator, IFbExpressionVisitor
{
private const string _letters = "bcdfghijklmnopqrstuvwxyz";
private static int _incrementLetter = 0;
private bool _isLegacyDialect;
private static readonly string _letters = "bcdfghijklmnopqrstuvwxyz";
private readonly bool _isLegacyDialect;
private readonly RelationalQueryCompilationContext _queryCompilationContext;
private readonly List<IQueryAnnotation> _queryAnnotations;

protected override string TypedTrueLiteral => "1";
protected override string TypedFalseLiteral => "0";
protected override string AliasSeparator => " ";

public FbQuerySqlGenerator(
QuerySqlGeneratorDependencies dependencies,
SelectExpression selectExpression,
IFbOptions fBOptions)
: base(dependencies, selectExpression)
=> _isLegacyDialect = fBOptions.IsLegacyDialect;
{
_isLegacyDialect = fBOptions.IsLegacyDialect;
_queryCompilationContext = CompileRQCC()(selectExpression);

public override Expression VisitTable(TableExpression tableExpression)
_queryAnnotations = _queryCompilationContext
.QueryAnnotations
.Where(p =>
p.GetType() == typeof(IncludeResultOperator)
|| p.GetType() == typeof(WithLockResultOperator))
.Distinct()
.ToList();
}

private static Func<SelectExpression, RelationalQueryCompilationContext> CompileRQCC()
{
var fieldInfo = typeof(SelectExpression)
.GetTypeInfo()
.GetRuntimeFields()
.Single(f => f.FieldType == typeof(RelationalQueryCompilationContext));

var parameterExpression = Expression.Parameter(
typeof(SelectExpression),
"selectExpression");

return Expression
.Lambda<Func<SelectExpression, RelationalQueryCompilationContext>>(
Expression.Field(parameterExpression, fieldInfo),
parameterExpression)
.Compile();
}

public override Expression VisitSelect(SelectExpression selectExpression)
{
var visitSelectExpression = base.VisitSelect(selectExpression);

if (_queryAnnotations.Count == 1
&& _queryAnnotations[0] is WithLockResultOperator annotation)
{
Sql.Append(annotation.Hint);
}

return visitSelectExpression;
}

public override Expression VisitTable(TableExpression tableExpression)
{
if (_isLegacyDialect)
{
if (tableExpression.Alias.IndexOf(".", StringComparison.OrdinalIgnoreCase) > -1
Expand Down

0 comments on commit db46134

Please sign in to comment.