Skip to content

Commit

Permalink
implements dotnet#18845
Browse files Browse the repository at this point in the history
  • Loading branch information
scomert committed Sep 13, 2020
1 parent 100a42b commit 3289269
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class SqliteOptionsExtension : RelationalOptionsExtension
{
private DbContextOptionsExtensionInfo _info;
private bool _loadSpatialite;
private bool _enableRegex;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -42,6 +43,7 @@ protected SqliteOptionsExtension([NotNull] SqliteOptionsExtension copyFrom)
: base(copyFrom)
{
_loadSpatialite = copyFrom._loadSpatialite;
_enableRegex = copyFrom._enableRegex;
}

/// <summary>
Expand Down Expand Up @@ -86,6 +88,29 @@ public virtual SqliteOptionsExtension WithLoadSpatialite(bool loadSpatialite)
return clone;
}

/// <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 virtual bool EnableRegex => _enableRegex;

/// <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 virtual SqliteOptionsExtension WithEnableRegex()
{
var clone = (SqliteOptionsExtension)Clone();

clone._enableRegex = true;

return clone;
}

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,13 @@ public SqliteDbContextOptionsBuilder([NotNull] DbContextOptionsBuilder optionsBu
: base(optionsBuilder)
{
}

/// <summary>
/// Configures enabling Regex.IsMatch method translation
/// </summary>
public virtual SqliteDbContextOptionsBuilder EnableRegex()
{
return WithOption(e => e.WithEnableRegex());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat
new SqliteByteArrayMethodTranslator(sqlExpressionFactory),
new SqliteDateTimeAddTranslator(sqlExpressionFactory),
new SqliteMathTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory)
new SqliteStringMethodTranslator(sqlExpressionFactory),
new SqliteRegexTranslator(sqlExpressionFactory)
});
}
}
Expand Down
66 changes: 66 additions & 0 deletions src/EFCore.Sqlite.Core/Query/Internal/SqliteRegexTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.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 SqliteRegexTranslator : IMethodCallTranslator
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 SqliteRegexTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
/// <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 SqlExpression Translate(SqlExpression instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

var regexIsMatchMethod = typeof(Regex).GetMethod(nameof(Regex.IsMatch), new Type[] { typeof(string), typeof(string) });

if (method == regexIsMatchMethod)
{
return _sqlExpressionFactory.Function("regexp",
arguments,
false,
new[] { false, false },
typeof(bool),
arguments[0].TypeMapping);
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Diagnostics;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class SqliteRelationalConnection : RelationalConnection, ISqliteRelationa
private readonly IDiagnosticsLogger<DbLoggerCategory.Infrastructure> _logger;
private readonly bool _loadSpatialite;
private readonly int? _commandTimeout;
private readonly bool _enableRegex;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -59,6 +61,7 @@ public SqliteRelationalConnection(
if (optionsExtension != null)
{
_loadSpatialite = optionsExtension.LoadSpatialite;
_enableRegex = optionsExtension.EnableRegex;

var relationalOptions = RelationalOptionsExtension.Extract(dependencies.ContextOptions);
_commandTimeout = relationalOptions.CommandTimeout;
Expand Down Expand Up @@ -114,6 +117,21 @@ private void InitializeDbConnection(DbConnection connection)
sqliteConnection.DefaultTimeout = _commandTimeout.Value;
}

if (_enableRegex)
{
sqliteConnection.CreateFunction<string, string, bool>(
"regexp",
(pattern, input) =>
{
if (input == null || pattern == null)
{
return false;
}
return Regex.IsMatch(pattern, input);
});
}

sqliteConnection.CreateFunction<object, object, object>(
"ef_mod",
(dividend, divisor) =>
Expand Down
70 changes: 70 additions & 0 deletions test/EFCore.Sqlite.FunctionalTests/RegexIsMatchTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

namespace Microsoft.EntityFrameworkCore
{
public class RegexIsMatchTest : IClassFixture<RegexIsMatchFixture>
{
public RegexIsMatchTest(RegexIsMatchFixture fixture)
=> Fixture = fixture;
protected RegexIsMatchFixture Fixture { get; }

[ConditionalFact]
public void Can_use_Regex_IsMatch()
{
using (var context = CreateContext())
{
var honeyDijon = context.Add(new User { Name = "Test1" }).Entity;
var buffaloBleu = context.Add(new User { Name = "NTest2" }).Entity;
var buffaloBleuNull = context.Add(new User { Name = null }).Entity;

context.SaveChanges();

var startingWithT = context.Users.Where(f => Regex.IsMatch(f.Name, @"^T"));

var nullPattern = context.Users.Where(f => Regex.IsMatch(f.Name, null));

Assert.Single(startingWithT);
Assert.Empty(nullPattern);
}
}

private RegexIsMatchContext CreateContext()
=> Fixture.CreateContext();
}
public class RegexIsMatchFixture : SharedStoreFixtureBase<RegexIsMatchContext>
{
protected override string StoreName { get; } = "RegexIsMatchTest";

protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;

protected override Type ContextType
=> typeof(RegexIsMatchContext);

public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
return base.AddOptions(builder).UseSqlite(f => f.EnableRegex());
}
}

public class RegexIsMatchContext : PoolableDbContext
{
public RegexIsMatchContext(DbContextOptions options)
: base(options)
{
}

public DbSet<User> Users { get; set; }
}

public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,16 @@ public void Can_add_extension_with_connection_using_generic_options()
Assert.Same(connection, extension.Connection);
Assert.Null(extension.ConnectionString);
}

[ConditionalFact]
public void Can_add_extension_with_enable_regex()
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite("Database=Crunchie", b => b.EnableRegex()) ;

var extension = optionsBuilder.Options.Extensions.OfType<SqliteOptionsExtension>().Single();

Assert.True(extension.EnableRegex);
}
}
}

0 comments on commit 3289269

Please sign in to comment.