Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sql.Regexp #4392

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft

Add Sql.Regexp #4392

wants to merge 3 commits into from

Conversation

jogibear9988
Copy link
Member

@jogibear9988 jogibear9988 commented Jan 26, 2024

supersedes #1077

Fixes #698

@jogibear9988
Copy link
Member Author

in #1077 you wrote I should create an extension lib.

I could do this, but could I also add this expression https://github.com/linq2db/linq2db/pull/4392/files#diff-2a6a9163861fbdc4015c6f32a3373c85469cb401552aea366b2d2c07ede63fb5R1198
in an external lib?

I would like to have the support for Regex in Query.
And should this be an extra lin in linq2db, or should it be my own?

[Expression(PN.PostgreSQL, "{0} ~ {1}", ServerSideOnly = true, IsPredicate = true)]
[Expression(PN.Oracle, "REGEXP_LIKE({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
[Expression(PN.SapHana, "{0} REGEXP {1}", ServerSideOnly = true, IsPredicate = true)]
[Expression(PN.SqlServer, "dbo.regex({0}, {1})", ServerSideOnly = true, IsPredicate = true)] // supported via https://github.com/infiniteloopltd/SQLServerRegex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove this line; sql server should throw an exception, and if consumers want to use regex, they can add the library specifically. Having this will create a false expectation for the consumer, if they look at the failed query and see dbo.regex() in the querystring, and wonder why they don't have the regex method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree, I wouldn't commit core linq2db functions to custom libraries.
What if MS adds Regex support in the future (quite possible) and it's not exactly the same dialect / not 100% compatible with this lib?

If that lib is popular enough to be included in main linq2db lib, I'd put it on a specific static class: InfiniteLoop.Regex(..).

[Expression(PN.Oracle, "REGEXP_LIKE({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
[Expression(PN.SapHana, "{0} REGEXP {1}", ServerSideOnly = true, IsPredicate = true)]
[Expression(PN.SqlServer, "dbo.regex({0}, {1})", ServerSideOnly = true, IsPredicate = true)] // supported via https://github.com/infiniteloopltd/SQLServerRegex
public static bool RegExpIsMatch(string text, string expression)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add xmldocs for this method.

@sdanyliv
Copy link
Member

I'm still against using new Regex() in LINQ queries. Don't. Each database engine has it's own regular expression dialect non comparable with .NET Regex . We discussed that several times!

Introduce only Sql.Regexp() function instead RegExpIsMatch and do not create Expression mapping

@jogibear9988
Copy link
Member Author

It's okay for me, if I could support it in a extension, so I could add my own "Expressions" so it works on my system with std regex

@MaceWindu MaceWindu added this to the In-progress milestone Jan 26, 2024
@viceroypenguin
Copy link
Contributor

I'm still against using new Regex() in LINQ queries. Don't. Each database engine has it's own regular expression dialect non comparable with .NET Regex . We discussed that several times!

Introduce only Sql.Regexp() function instead RegExpIsMatch and do not create Expression mapping

@jogibear9988 this is a really good point in the context of source-generated regular expressions, which may not operate as expected in this context. Like sdanyliv said, it will create the expectation that the db will operate on the same regex rules that .net does, which is not true for any db engine.

@viceroypenguin viceroypenguin changed the title fixes #698 - add regexp support Add Sql.Regexp Jan 26, 2024
@jogibear9988
Copy link
Member Author

jogibear9988 commented Jan 26, 2024

Yes, I wanted to now, is there a way for me, to add the line I did in Expressions.cs later in my usercode?

So that in my code we still could use the "Regexp" object?

We mostly use simple Regexes, wich would worknearly in all DBs.
(and in sqlserver and sqlite they would in any case, cause there C# code is called)

@@ -10,5 +10,13 @@ public static bool GenerateScopeIdentity
get => SqlServerOptions.Default.GenerateScopeIdentity;
set => SqlServerOptions.Default = SqlServerOptions.Default with { GenerateScopeIdentity = value };
}

/// <summary>
/// You can specify a UserDefinedFunction wich is called to run a regex against your data.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in "wich".

/// Look at https://github.com/infiniteloopltd/SQLServerRegex for a sample
/// in this case the value would be "dbo.regex({0}, {1})"
/// </summary>
public static string? RegexUserDefinedFunctionName { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to have such specific options? It's basically exactly the same level of abstraction as [Expression] but for a single specific function.
We could imagine similar options for many other SQL libraries -- and I really don't think we want to do that.
Moreover, I'd rather reserve the "official" linq2db regex function to a possible future "native" regex function in SQL Server (should it ever happen).

I'd rather have:

  • Docs/fully working example code to create such a static function with [Expression("dbo.regex({0}, {1})")].
  • Shipping such functions in different namespaces that can be imported (using LinqToDB.InfiniteLoop) and/or different libraries.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move the complete code to an external library, if someone could tell me, how I could accomplish the add of the C# regex class to Expressions.cs, so I could use that in my queries

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jogibear9988 This PR is currently doing a (string, string) extension, isn't it?
Do you mean you'd like to be able to handle Regex.IsMatch(..)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I'd like to use regex.ismatch in my queries

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna be honest, that's terrifying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But as I also saind, if we don't want to do this change, it is okay.

I'm only looking for a way, how I can extend Expressions.cs default... from outside.
Should we add a method? Problem is, that once initialized, adding something to it, does not have an effect. So it needs refactoring

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your approche is to limited.

Can you elaborate? Seems much more generic and flexible to me. In any case, it covers dbo.regex({1}, {2}) that you're wanting in this PR.

Here's why I think your approach is limited and worst:

  • You introduce a new option RegexUserDefinedFunctionName that will only work for Regex.IsMatch. No other function is supported. Right now there's PR Support for NodaTime #3624 that's adding a lot of ugly code to support NodaTime that would benefit from a generic way to do this.
  • RegexUserDefinedFunctionName is static. What if I want a different config for two different DB instances? How could we easily run tests in parallel?

IMHO this design does not scale to more functions/libraries and is not easily maintainable long-term.
It moves inside linq2db core something that could trivially be done in user-land.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only looking for a way, how I can extend Expressions.cs default... from outside.
Should we add a method? Problem is, that once initialized, adding something to it, does not have an effect. So it needs refactoring

I think this is the way to go and it's basically my proposal above.
Yes, it's a non-trivial refactoring but I think it is the best option long-term. Only issue right now is that there's the large @sdanyliv parser refactoring going on. We need to merge this before doing other large changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay for me

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW will gladly help with this. It's been on my mind for a while.

@jogibear9988
Copy link
Member Author

efcore seem to support this for postgres:

https://www.npgsql.org/efcore/mapping/translations.html#string-functions

image

@jogibear9988
Copy link
Member Author

and also in sqlite they seem to support them:

image

dotnet/efcore#28078

@jogibear9988
Copy link
Member Author

They also seem to automaticly map a regex function for sqlite (if you'd like to)

dotnet/efcore#18845

@jods4 jods4 mentioned this pull request Jan 27, 2024
@sdanyliv
Copy link
Member

It is Roji's decision because he created PostgreSQL provider, and probably ported to SQLite. And I think he is wrong here.

@jogibear9988
Copy link
Member Author

I don't think so ;-) , but I'll wait till we have a possibility to add my own mappings for Standard methods.
I also need it for the Guid.ToString call in my other pull request.

@jogibear9988
Copy link
Member Author

So, I now removed the functionality from Linq2db and added a sample in the Tests.
So maybe we can merge this, it test if regex registering is possible and usable.
I this it's usefull as a information.

@jogibear9988 jogibear9988 marked this pull request as ready for review January 29, 2024 08:52
@viceroypenguin
Copy link
Contributor

@jogibear9988 looks like you remove both the expression and the Sql.Regexp method. You will need to restore the Sql. method.

@jogibear9988
Copy link
Member Author

@jogibear9988 looks like you remove both the expression and the Sql.Regexp method. You will need to restore the Sql. method.

No, I moved everything to an extra file. Cause for me it only makes sense if SqlServer is also supported. So I only added it to the tests as a sample. If someone likes to use it, he could copy the file from the tests. Or maybe I create a extra Nuget for Linq2db -> For Example Linq2DB.Regex, wich then maybe also contains the assembly for sqlserver

@jogibear9988
Copy link
Member Author

So we can merge this, as a sample, or close the pull req.
As I can now add the support from outside it's okay

@viceroypenguin
Copy link
Contributor

Given that the code doesn't build without changes to the Sql. class, I'll go ahead and close it.

@jogibear9988
Copy link
Member Author

@viceroypenguin now I understand, it should build. This was wrong, I'll fix it

@jogibear9988 jogibear9988 reopened this Jan 29, 2024
[LinqToDB.Sql.Expression(ProviderName.Oracle, "REGEXP_LIKE({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.SapHana, "{0} REGEXP {1}", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.SqlServer, "dbo.regex({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
public static bool IsMatch(string text, string expression)
[LinqToDB.Sql.Expression(ProviderName.PostgreSQL, "{0} ~ {1}", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.Oracle, "REGEXP_LIKE({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.SapHana, "{0} REGEXP {1}", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.SqlServer, "dbo.regex({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't map non-standard 3rd-party functions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's only a sample class in the tests. but I can remove

[LinqToDB.Sql.Expression(ProviderName.Oracle, "REGEXP_LIKE({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.SapHana, "{0} REGEXP {1}", ServerSideOnly = true, IsPredicate = true)]
[LinqToDB.Sql.Expression(ProviderName.SqlServer, "dbo.regex({0}, {1})", ServerSideOnly = true, IsPredicate = true)]
public static bool IsMatch(string text, string expression)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing overloads for additional parameters, supported by some DBs (e.g. options)

/// For SQLServer support you need to register the function from
/// https://github.com/infiniteloopltd/SQLServerRegex
/// </summary>
public static void AddRegexSupport()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to avoid explicit registrations. Wether we register it automatically or don't map members at all

EnableSqliteRegex(connection.Connection);
}

public static void EnableSqliteRegex(DbConnection connection)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need it. User could register function from his code, if he needs it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at efcore, there EF does the registration for you

}

[Test]
public void Test698([IncludeDataSources(TestProvName.AllSQLite, TestProvName.AllPostgreSQL, TestProvName.AllOracle)] string context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all supported databases and APIs should be tested

@MaceWindu MaceWindu marked this pull request as draft January 30, 2024 17:04
@jogibear9988
Copy link
Member Author

@MaceWindu whats the advantage to use this instead of Linq.Expressions.MapMember?

@MaceWindu
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Use Regex in Query
6 participants