Regex translation: `Regex.Replace` substitutions are passed through, `Singleline | Multiline` gets the wrong flag, and `[^x]` never matches a newline

オープン
#4,094 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
csharp, postgresql, sql
領域
database

調査の方向性

Start in src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRegexTranslator.cs (lines 111-119, 166-170) and src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs (lines 1022-1030) to see how regex flags and substitutions are emitted. Run the repro DbContext from the issue to observe mismatched SQL. Done means replacements use \1-style substitutions and the w flag for Singleline|Multiline, or a docs note covers gaps.

索引モデルが issue の本文から書いたものです。

説明

Environment
PostgreSQL 18.6 (postgres:18), session TimeZone Etc/UTC
.NET 10 runtime 10.0.11, Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3, EF Core 10.0.4
.NET 11 runtime 11.0.0-rc.2, Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-rc.2, EF Core 11.0.0-rc.2
Also Npgsql 10.0.3 on both lines, macOS arm64

Both lines print the same output. main at 150011d has the same code.

LINQ Input .NET PostgreSQL SQL
Regex.Replace(r.Text, "(a)", "[$1]") abc [a]bc [$1]bc regexp_replace(r."Text", '(a)', '[$1]', 'p')
Regex.IsMatch(r.Text, "a.b", RegexOptions.Singleline | RegexOptions.Multiline) a\nb match no match r."Text" ~ '(?n)a.b'
Regex.IsMatch(r.Text, "a[^x]b") a\nb match no match r."Text" ~ '(?p)a[^x]b'
Repro
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;

await using var context = new TestContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

context.Rows.AddRange(new Row { Text = "abc" }, new Row { Text = "a\nb" });
await context.SaveChangesAsync();
var rows = await context.Rows.AsNoTracking().OrderBy(r => r.Id).ToListAsync();

// 1. .NET substitution syntax is passed through; PostgreSQL only knows \1..\9 and \&
Console.WriteLine($"Regex.Replace(\"abc\", \"(a)\", \"[$1]\"): .NET {Regex.Replace(rows[0].Text, "(a)", "[$1]")}, database {await context.Rows.Where(r => r.Id == 1).Select(r => Regex.Replace(r.Text, "(a)", "[$1]")).SingleAsync()}");

// 2. Singleline makes . match \n, also together with Multiline
Console.WriteLine($"IsMatch(\"a\\nb\", \"a.b\", Singleline | Multiline): .NET {rows.Count(r => Regex.IsMatch(r.Text, "a.b", RegexOptions.Singleline | RegexOptions.Multiline))}, database {await context.Rows.CountAsync(r => Regex.IsMatch(r.Text, "a.b", RegexOptions.Singleline | RegexOptions.Multiline))}");

// 3. A negated bracket matches \n in .NET
Console.WriteLine($"IsMatch(\"a\\nb\", \"a[^x]b\"): .NET {rows.Count(r => Regex.IsMatch(r.Text, "a[^x]b"))}, database {await context.Rows.CountAsync(r => Regex.IsMatch(r.Text, "a[^x]b"))}");

public class TestContext : DbContext
{
    public DbSet<Row> Rows => Set<Row>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseNpgsql(Environment.GetEnvironmentVariable("Test__Npgsql__DefaultConnection"), o => o.UseNodaTime())
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public class Row
{
    public int Id { get; set; }
    public string Text { get; set; } = "";
}

Output, the same on both lines:

Regex.Replace("abc", "(a)", "[$1]"): .NET [a]bc, database [$1]bc
IsMatch("a\nb", "a.b", Singleline | Multiline): .NET 1, database 0
IsMatch("a\nb", "a[^x]b"): .NET 1, database 0
  • Substitutions are passed through unchanged (NpgsqlRegexTranslator.cs#L111-L119). PostgreSQL expands \1–\9 and \&. .NET uses $1, ${name}, $& and $$, and treats a backslash literally. So $1 comes out as $1, and \1 means different things on the two sides. A constant replacement could be rewritten; a column replacement can't easily be.
  • Singleline | Multiline renders n because the Multiline check comes first (NpgsqlQuerySqlGenerator.cs#L1022-L1030, NpgsqlRegexTranslator.cs#L166-L170). With n, . doesn't match a newline. .NET's Singleline with Multiline corresponds to w: ^ and $ per line, and . matches newlines.
  • The default p flag, which is there so that . doesn't match a newline, also stops a negated bracket like [^x] from matching one. .NET's default lets [^x] match \n. There may be no flag combination that matches .NET exactly, so a docs note may be the realistic fix here. The same goes for other dialect differences, for example \b, which is a backspace in PostgreSQL's ARE (a word boundary there is \y).

Regex.Replace on 10.0.3 and 11.0.0-rc.2 still uses 'p' without g. That is already fixed on main and in hotfix/10.0.4 (#4035).

主要言語
C#
スター
1.8k
フォーク
268
平均マージ
2時間 35分
マージ済み PR(30日)
136

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

npgsql/efcore.pg のほかの issue

npgsql/efcore.pg の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。