You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Regex.Replace is translated to regexp_replace without the g flag — only the first match is replaced #4035
Regex.Replace(input, pattern, replacement) replaces all matches in .NET, but the translation produced by NpgsqlRegexTranslator.TranslateReplace calls PostgreSQL regexp_replace without the g flag, which replaces only the first
match. The same LINQ query therefore returns different results when evaluated on the client and when translated to SQL.
TranslateOptions never emits g: for RegexOptions.None it returns p, and for RegexOptions.Singleline it returns an empty
string, so regexp_replace gets only three arguments — in both cases without g. (Current main, src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRegexTranslator.cs.)
Expected: one row — in .NET Regex.Replace("+48 (600) 123-456", "[^0-9]", "") is "48600123456".
Actual: no rows. Generated SQL:
SELECT c."Id", c."Phone"FROM"Contacts"AS c
WHERE regexp_replace(c."Phone", '[^0-9]', '', 'p') LIKE'%600123456%'
With RegexOptions.Singleline the flags argument is dropped entirely, still without g:
WHERE regexp_replace(c."Phone", '[^0-9]', '') =''
regexp_replace('+48 (600) 123-456', '[^0-9]', '', 'p') returns '48 (600) 123-456' — only the leading + is removed.
Suggested fix
Append g to the flags passed to regexp_replace (and pass the flags argument also when the translated options are otherwise empty),
e.g. regexp_replace(input, pattern, replacement, 'pg') for RegexOptions.None and regexp_replace(input, pattern, replacement, 'g')
for RegexOptions.Singleline. Both translated overloads — static Regex.Replace(input, pattern, replacement) and Regex.Replace(input, pattern, replacement, options) — always replace every match in .NET, so g applies unconditionally.
Workaround
A user-defined function mapped with HasTranslation to regexp_replace(..., 'g').
Versions
Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 (also present on main)
The below is an AI-generated analysis and may contain inaccuracies.
Confirmed bug; issue type set to Bug. Reproduced the original predicate returning no rows, and reduced it to a projection that directly compares server and .NET replacement results. Tested against PostgreSQL 18.4.
Npgsql EF provider
Minimized projection result
9.0.4
Correct: 48600123456; replacement evaluated on the client
10.0.0, 10.0.2, 10.0.3
Incorrect: 48 (600) 123-456; replacement evaluated on the server
Regression assessment: This is not new in 10.0.3. The missing flag was already present when translation was introduced by #3062, first released in 10.0.0-preview.1. It is a behavioral regression for projections previously evaluated correctly on the client. The original Where query is a different case: on 9.0.4 it throws a translation exception, rather than returning the expected row, because Regex.Replace translation was not supported.
Root cause:TranslateReplace omits g. The default overload emits:
SELECT regexp_replace(c."Phone", '[^0-9]', '', 'p')
FROM"Contacts"AS c
LIMIT2
Also independently reproduced the four-argument .NET overload with RegexOptions.Singleline: it emits the three-argument PostgreSQL function and returns the same incorrect value. Direct SQL with flags pg and g, respectively, returns the expected 48600123456.
The suggested fix is appropriate: unconditionally append ginside TranslateReplace, including when the other flags are empty. Do not add it to the shared TranslateOptions, which is also used by Regex.Count. The existing replacement tests use anchored patterns such as ^A, which do not exercise multiple replacements with the test data. Coverage should include multiple matches for the default overload and explicit options, particularly None and Singleline.
Provider scope: This is an Npgsql translation bug. With SQLite 10.0.3, the minimized projection returns the correct result by evaluating Regex.Replace on the client; the original predicate throws a translation exception. SQLite therefore does not provide an equivalent server-side translation comparison, but does not exhibit the incorrect replacement result.
Duplicates: No likely duplicate found among open or closed issues in dotnet/efcore or this repository. #3059 and #3062 are the original feature request and implementation, not duplicate bug reports.
minimal repro
Use a .NET 10 console project referencing Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3. Set Test__Npgsql__DefaultConnection to a disposable database: this program deletes and recreates it.
Activity
roji commentedon Oct 19, 2026
AI Triage
The below is an AI-generated analysis and may contain inaccuracies.
Confirmed bug; issue type set to Bug. Reproduced the original predicate returning no rows, and reduced it to a projection that directly compares server and .NET replacement results. Tested against PostgreSQL 18.4.
48600123456; replacement evaluated on the client48 (600) 123-456; replacement evaluated on the serverRegression assessment: This is not new in 10.0.3. The missing flag was already present when translation was introduced by #3062, first released in 10.0.0-preview.1. It is a behavioral regression for projections previously evaluated correctly on the client. The original
Wherequery is a different case: on 9.0.4 it throws a translation exception, rather than returning the expected row, becauseRegex.Replacetranslation was not supported.Root cause:
TranslateReplaceomitsg. The default overload emits:Also independently reproduced the four-argument .NET overload with
RegexOptions.Singleline: it emits the three-argument PostgreSQL function and returns the same incorrect value. Direct SQL with flagspgandg, respectively, returns the expected48600123456.The suggested fix is appropriate: unconditionally append
ginsideTranslateReplace, including when the other flags are empty. Do not add it to the sharedTranslateOptions, which is also used byRegex.Count. The existing replacement tests use anchored patterns such as^A, which do not exercise multiple replacements with the test data. Coverage should include multiple matches for the default overload and explicit options, particularlyNoneandSingleline.Provider scope: This is an Npgsql translation bug. With SQLite 10.0.3, the minimized projection returns the correct result by evaluating
Regex.Replaceon the client; the original predicate throws a translation exception. SQLite therefore does not provide an equivalent server-side translation comparison, but does not exhibit the incorrect replacement result.Duplicates: No likely duplicate found among open or closed issues in
dotnet/efcoreor this repository. #3059 and #3062 are the original feature request and implementation, not duplicate bug reports.minimal repro
Use a .NET 10 console project referencing
Npgsql.EntityFrameworkCore.PostgreSQL10.0.3. SetTest__Npgsql__DefaultConnectionto a disposable database: this program deletes and recreates it.Fails with:
roji commentedon Oct 19, 2026
Thanks, fixing this for 10.0.4.