From e5fe454ade3a1ff121341f472f0ca1b422380247 Mon Sep 17 00:00:00 2001 From: Sylvain Pointeau Date: Thu, 18 Dec 2025 09:00:13 +0100 Subject: [PATCH 1/3] Empty string must not return null, add function IsMatch --- src/Regex.cs | 57 +++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/Regex.cs b/src/Regex.cs index d7f3028..409f0ec 100644 --- a/src/Regex.cs +++ b/src/Regex.cs @@ -8,54 +8,61 @@ public partial class UDF { [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true)] - public static SqlString Match(String input, String pattern) + public static SqlBoolean IsMatch(string input, string pattern) { - if (String.IsNullOrEmpty(input) || String.IsNullOrEmpty(pattern)) + if (input is null || string.IsNullOrEmpty(pattern)) { - return new SqlString(null); + return new SqlBoolean(false); } - else - { - Match m = Regex.Match(input, pattern); - return new SqlString(m.Success ? m.Value : null); + var success = Regex.IsMatch(input, pattern); + + return new SqlBoolean(success); + } + + [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true)] + public static SqlString Match(string input, string pattern) + { + if (input is null || string.IsNullOrEmpty(pattern)) + { + return new SqlString(null); } + + Match m = Regex.Match(input, pattern); + + return new SqlString(m.Success ? m.Value : null); } [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true)] - public static SqlString GroupMatch(String input, String pattern, String group) + public static SqlString GroupMatch(string input, string pattern, string group) { - if (String.IsNullOrEmpty(input) || String.IsNullOrEmpty(pattern) || String.IsNullOrEmpty(group)) + if (input is null || string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(group)) { return new SqlString(null); } - else - { - Group g = Regex.Match(input, pattern).Groups[group]; - return new SqlString(g.Success ? g.Value : null); - } + Group g = Regex.Match(input, pattern).Groups[group]; + + return new SqlString(g.Success ? g.Value : null); } [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true)] - public static SqlString Replace(String input, String pattern, String replacement) + public static SqlString Replace(string input, string pattern, string replacement) { // the replacement string is not checked for an empty string because that is a valid replacement pattern - if (String.IsNullOrEmpty(input) || String.IsNullOrEmpty(pattern) || replacement == null) + if (input is null || string.IsNullOrEmpty(pattern) || replacement == null) { return new SqlString(null); } - else - { - return new SqlString(Regex.Replace(input, pattern, replacement)); - } + + return new SqlString(Regex.Replace(input, pattern, replacement)); } [SqlFunction(DataAccess = DataAccessKind.None, FillRowMethodName = "FillMatches", TableDefinition = "Position int, MatchText nvarchar(max)")] - public static IEnumerable Matches(String input, String pattern) + public static IEnumerable Matches(string input, string pattern) { List MatchCollection = new List(); - if (!String.IsNullOrEmpty(input) && !String.IsNullOrEmpty(pattern)) + if (input != null && !string.IsNullOrEmpty(pattern)) { //only run through the matches if the inputs have non-empty, non-null strings foreach (Match m in Regex.Matches(input, pattern)) @@ -67,13 +74,13 @@ public static IEnumerable Matches(String input, String pattern) } [SqlFunction(DataAccess = DataAccessKind.None, FillRowMethodName = "FillMatches", TableDefinition = "Position int, MatchText nvarchar(max)")] - public static IEnumerable Split(String input, String pattern) + public static IEnumerable Split(string input, string pattern) { List MatchCollection = new List(); - if (!String.IsNullOrEmpty(input) && !String.IsNullOrEmpty(pattern)) + if (input != null && !string.IsNullOrEmpty(pattern)) { //only run through the splits if the inputs have non-empty, non-null strings - String[] splits = Regex.Split(input, pattern); + string[] splits = Regex.Split(input, pattern); for (int i = 0; i < splits.Length; i++) { MatchCollection.Add(new RegexMatch(i, splits[i])); From 742338a4b0e14ba542c5763c15f5b5c332c89731 Mon Sep 17 00:00:00 2001 From: Sylvain Pointeau Date: Thu, 18 Dec 2025 22:30:59 +0100 Subject: [PATCH 2/3] migrate to 4.8 --- src/sqlregex.sqlproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sqlregex.sqlproj b/src/sqlregex.sqlproj index db55c72..29924b1 100644 --- a/src/sqlregex.sqlproj +++ b/src/sqlregex.sqlproj @@ -20,7 +20,7 @@ Database false sql-server-regex - v4.0 + v4.8 From d6e9aeafec12011a6f4519775d8f0a6e0295350c Mon Sep 17 00:00:00 2001 From: Sylvain Pointeau Date: Thu, 18 Dec 2025 22:40:09 +0100 Subject: [PATCH 3/3] add doc --- README.md | 4 ++ examples/is-match.md | 96 ++++++++++++++++++++++++++++++++++++++++ sql/scalar_functions.sql | 8 ++++ 3 files changed, 108 insertions(+) create mode 100644 examples/is-match.md diff --git a/README.md b/README.md index 788a426..6b6495e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ SQL-Server-Regex gives you the power to run regular expressions inside SQL Serve # Examples +### IsMatch + +The [RegexIsMatch()](/examples/is-match.md) scalar function evaluates a regular expression against a string and returns 1 when a match is found, or 0 otherwise. + ### Match The [RegexMatch()](/examples/match.md) scalar function lets you call a regular expression against a string, and returns the first result if there is a match. diff --git a/examples/is-match.md b/examples/is-match.md new file mode 100644 index 0000000..0bcbc9f --- /dev/null +++ b/examples/is-match.md @@ -0,0 +1,96 @@ +# IsMatch + +![SQL Regex Logo](/images/sql-regex-logo.png) + +RegexIsMatch() is a scalar function that lets you test whether a regular expression matches a string. It is a SQL CLR function that exposes the [System.Text.RegularExpressions](https://msdn.microsoft.com/en-us/library/system.text.regularexpressions(v=vs.110).aspx)' [IsMatch()](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch?view=net-10.0) method. + +It returns: + +- **1** if the pattern matches +- **0** otherwise + +The function is intended for **validation**, not extraction. + +Let's look at a few examples, inspired by a handy [Regular Expressions tutorial](http://www.regular-expressions.info/examples.html) + +--- + +## Examples + +### Digits + +Check whether a string contains at least one digit. + +```sql +select dbo.RegexIsMatch('ABC123', '\d+'); -- 1 +select dbo.RegexIsMatch('ABC', '\d+'); -- 0 +``` + +--- + +### Letters only (whole string) + +Validate that the entire string contains only letters. + +```sql +select dbo.RegexIsMatch('Hello', '^[A-Za-z]+$'); -- 1 +select dbo.RegexIsMatch('Hi!', '^[A-Za-z]+$'); -- 0 +``` + +--- + +### Empty or exactly two characters + +```sql +select dbo.RegexIsMatch('', '^(.{2})?$'); -- 1 +select dbo.RegexIsMatch('AB', '^(.{2})?$'); -- 1 +select dbo.RegexIsMatch('A', '^(.{2})?$'); -- 0 +``` + +--- + +### No underscore allowed + +Validate that a string does not contain `_`. + +```sql +select dbo.RegexIsMatch('abc', '^[^_]+$'); -- 1 +select dbo.RegexIsMatch('abc_', '^[^_]+$'); -- 0 +``` + +--- + +### Whitespace only + +Check if a string is empty or contains only whitespace. + +```sql +select dbo.RegexIsMatch('', '^\s*$'); -- 1 +select dbo.RegexIsMatch(' ', '^\s*$'); -- 1 +select dbo.RegexIsMatch('abc', '^\s*$'); -- 0 +``` + +--- + +### Partial match vs full-string match + +`RegexIsMatch()` succeeds if the pattern matches **anywhere** in the string. + +```sql +select dbo.RegexIsMatch('abc_', '[^_]+'); -- 1 (matches "abc") +``` + +To validate the **entire** string, use anchors: + +```sql +select dbo.RegexIsMatch('abc_', '^[^_]+$'); -- 0 +select dbo.RegexIsMatch('abc', '^[^_]+$'); -- 1 +``` + +--- + +## Notes + +- Use `^` and `$` when validating full values. +- The function uses the .NET regular expression engine. +- For extracting matched text, use `RegexMatch()` instead. \ No newline at end of file diff --git a/sql/scalar_functions.sql b/sql/scalar_functions.sql index 065aff0..19404b8 100644 --- a/sql/scalar_functions.sql +++ b/sql/scalar_functions.sql @@ -1,6 +1,9 @@ use Scratch go +drop function dbo.RegexIsMatch +go + drop function dbo.RegexMatch go @@ -12,6 +15,11 @@ go -- see https://msdn.microsoft.com/en-us/library/ms186755.aspx for details +CREATE FUNCTION dbo.RegexIsMatch (@input nvarchar(max), @pattern nvarchar(max)) +RETURNS bit +AS EXTERNAL NAME [RegexAssembly].[UDF].[IsMatch] +go + CREATE FUNCTION dbo.RegexMatch (@input nvarchar(max), @pattern nvarchar(max)) RETURNS nvarchar(max) AS EXTERNAL NAME [RegexAssembly].[UDF].[Match]