fix(sql-dialect): enforce Postgres/Oracle identifier ceilings in UTF-8 bytes (Driver.Sql.Dialect-001)
NAMEDATALEN-1 (63) and Oracle 12.2's 128 are byte limits in the database encoding, not character counts, so a 32-character Cyrillic name passed ident.Length and was then silently truncated (Postgres) or rejected by the server (Oracle). Both dialects now measure Encoding.UTF8.GetByteCount and say 'bytes' in the rejection. SqlServerDialect deliberately keeps ident.Length — sysname is nvarchar(128), a real character limit — pinned by a control test.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
||||
@@ -24,6 +25,9 @@ public sealed class OracleDialect : ISqlDialect
|
||||
/// <summary>
|
||||
/// Oracle 12.2+'s identifier ceiling. Enforced because a longer string cannot name a real catalog
|
||||
/// object — it is either a config mistake or padding, and rejecting it keeps the quoted output bounded.
|
||||
/// <para><b>The unit is BYTES in the database character set, not characters</b> — see
|
||||
/// <see cref="QuoteIdentifier"/>. Practically that means UTF-8 (<c>AL32UTF8</c>) on any modern estate,
|
||||
/// so a 65-character Cyrillic name is already over the limit.</para>
|
||||
/// </summary>
|
||||
internal const int MaxIdentifierLength = 128;
|
||||
|
||||
@@ -187,8 +191,14 @@ public sealed class OracleDialect : ISqlDialect
|
||||
/// (<c>ALL_TAB_COLUMNS.column_name</c>, i.e. what the server actually stored) before anything reaches
|
||||
/// this method — an operator who authored <c>speed</c> against an Oracle <c>SPEED</c> column resolves
|
||||
/// to <c>SPEED</c> at Initialize, and it is <c>SPEED</c> that gets quoted.</para>
|
||||
/// <para><b>The length ceiling is measured in UTF-8 bytes, not characters.</b> Oracle's 128 bounds the
|
||||
/// name in the <em>database character set</em> (<c>AL32UTF8</c> on any modern estate), so counting
|
||||
/// UTF-16 code units would accept a 65-character Cyrillic name — 130 bytes — and let it fail at the
|
||||
/// server with <c>ORA-00972</c> rather than here, where the message can name the tag.
|
||||
/// <c>SqlServerDialect</c> deliberately does <b>not</b> do this: T-SQL's <c>sysname</c> is
|
||||
/// <c>nvarchar(128)</c>, a genuine 128-character limit.</para>
|
||||
/// <para><b>Rejects</b> (all as <see cref="ArgumentException"/>): null / empty / all-whitespace; longer
|
||||
/// than <see cref="MaxIdentifierLength"/>; any control character (which covers embedded NUL, and the
|
||||
/// than <see cref="MaxIdentifierLength"/> <b>bytes</b>; any control character (which covers embedded NUL, and the
|
||||
/// C0/C1 ranges that can truncate or corrupt a logged/audited statement); and any Unicode
|
||||
/// <see cref="UnicodeCategory.Format"/> character — bidi overrides, zero-width spaces, soft hyphen,
|
||||
/// BOM. Those last cannot escape the quotes, but they spoof the <em>rendered</em> text of a log line or
|
||||
@@ -209,9 +219,13 @@ public sealed class OracleDialect : ISqlDialect
|
||||
throw new ArgumentException(
|
||||
"A SQL identifier must be a non-empty, non-whitespace name.", nameof(ident));
|
||||
|
||||
if (ident.Length > MaxIdentifierLength)
|
||||
// Byte count, not ident.Length: Oracle's 128 is measured in the database character set, so a
|
||||
// 65-character Cyrillic name is 130 UTF-8 bytes and the server answers ORA-00972. Counting UTF-16
|
||||
// code units would send it anyway, failing at the server for a reason this dialect could have named.
|
||||
var byteLength = Encoding.UTF8.GetByteCount(ident);
|
||||
if (byteLength > MaxIdentifierLength)
|
||||
throw new ArgumentException(
|
||||
$"An Oracle identifier may not exceed {MaxIdentifierLength} characters (got {ident.Length}).",
|
||||
$"An Oracle identifier may not exceed {MaxIdentifierLength} bytes (got {byteLength}).",
|
||||
nameof(ident));
|
||||
|
||||
foreach (var ch in ident)
|
||||
|
||||
Reference in New Issue
Block a user