fix(localdb): escape apostrophes in apply/snapshot JSON paths (LocalDb-006)
A column name containing ' was captured fine but ended the JSON-path SQL literal early on the apply and snapshot paths, dead-lettering every row of the table on the peer and faulting the snapshot page query for a quoted PK column. Both paths now share JsonPathSql.For, which doubles ' after the JSON-path escape. 2-node delta and wiped-peer snapshot tests fail pre-fix. (scadaproj#46) Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
namespace ZB.MOM.WW.LocalDb.Replication.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Builds the SQL string literal for a JSON path that selects one column's member out of a captured
|
||||
/// <c>row_json</c> / <c>pk_json</c> object. Shared by <see cref="LwwApplier"/> and
|
||||
/// <see cref="SnapshotStreamer"/> so the two cannot drift apart again.
|
||||
/// </summary>
|
||||
internal static class JsonPathSql
|
||||
{
|
||||
/// <summary>
|
||||
/// <c>'$."col"'</c>: a quoted key member, so a column name with reserved characters still
|
||||
/// resolves. Two escaping layers, applied in order: the column's <c>"</c> is backslash-escaped
|
||||
/// for the JSON path grammar, then every <c>'</c> in the resulting path is doubled for the SQL
|
||||
/// string literal around it. Column names come from <c>PRAGMA table_info</c> and are not
|
||||
/// regex-gated the way table names are, and capture (<c>TriggerSqlGenerator</c>) already accepts
|
||||
/// any name — so a column called <c>it's</c> used to be captured, then end this literal early
|
||||
/// and dead-letter every row of its table on the peer (LocalDb-006).
|
||||
/// </summary>
|
||||
public static string For(string column)
|
||||
{
|
||||
var path = "$.\"" + column.Replace("\"", "\\\"") + "\"";
|
||||
return "'" + path.Replace("'", "''") + "'";
|
||||
}
|
||||
}
|
||||
@@ -388,9 +388,6 @@ internal sealed class LwwApplier(SqliteLocalDb db, Func<DateTimeOffset>? utcNow
|
||||
// Local copy of TriggerSqlGenerator's convention (that helper lives in the core assembly).
|
||||
private static string Ident(string name) => "\"" + name.Replace("\"", "\"\"") + "\"";
|
||||
|
||||
// A single-quoted JSON path with a quoted key member, so a column name with reserved
|
||||
// characters still resolves: $."col". The key's double quotes are backslash-escaped per
|
||||
// JSON path grammar.
|
||||
private static string JsonPath(string column) => "'$.\"" + column.Replace("\"", "\\\"") + "\"'";
|
||||
private static string JsonPath(string column) => JsonPathSql.For(column);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,9 +234,7 @@ internal sealed class SnapshotStreamer(SqliteLocalDb db, OplogStore store, Repli
|
||||
|
||||
private static string StringLiteral(string value) => "'" + value.Replace("'", "''") + "'";
|
||||
|
||||
// A single-quoted JSON path with a quoted key member so a column name with reserved characters
|
||||
// still resolves: $."col".
|
||||
private static string JsonPath(string column) => "'$.\"" + column.Replace("\"", "\\\"") + "\"'";
|
||||
private static string JsonPath(string column) => JsonPathSql.For(column);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user