Improve NpgsqlCodec whitespace and quotation rules
This commit is contained in:
parent
18e737e865
commit
4c7a6c2666
2 changed files with 44 additions and 21 deletions
|
|
@ -58,11 +58,12 @@ public class NpgsqlCodecTests
|
||||||
var s = res.Value;
|
var s = res.Value;
|
||||||
Assert.Contains("Host=db.example.com", s);
|
Assert.Contains("Host=db.example.com", s);
|
||||||
Assert.Contains("Port=5432", s);
|
Assert.Contains("Port=5432", s);
|
||||||
Assert.Contains("Database=\"prod db\"", s);
|
Assert.Contains("Database=prod db", s);
|
||||||
Assert.Contains("Username=bob", s);
|
Assert.Contains("Username='bob '", s);
|
||||||
Assert.Contains("Password=\"p;ss\"\"word\"", s);
|
// Contains double-quote, no single-quote -> prefer single-quoted per DbConnectionStringBuilder-like behavior
|
||||||
|
Assert.Contains("Password='p;ss" + '"' + "word'", s);
|
||||||
Assert.Contains("SSL Mode=VerifyFull", s);
|
Assert.Contains("SSL Mode=VerifyFull", s);
|
||||||
Assert.Contains("Application Name=\"cli app\"", s);
|
Assert.Contains("Application Name=cli app", s);
|
||||||
Assert.Contains("Timeout=9", s);
|
Assert.Contains("Timeout=9", s);
|
||||||
Assert.Contains("Search Path=public", s);
|
Assert.Contains("Search Path=public", s);
|
||||||
}
|
}
|
||||||
|
|
@ -77,11 +78,12 @@ public class NpgsqlCodecTests
|
||||||
var formatted = codec.TryFormat(parsed.Value);
|
var formatted = codec.TryFormat(parsed.Value);
|
||||||
Assert.True(formatted.IsSuccess);
|
Assert.True(formatted.IsSuccess);
|
||||||
var s = formatted.Value;
|
var s = formatted.Value;
|
||||||
Assert.Contains("Host=\"my host\"", s);
|
Assert.Contains("Host=my host", s);
|
||||||
Assert.Contains("Database=postgres", s);
|
Assert.Contains("Database=postgres", s);
|
||||||
Assert.Contains("Username=me", s);
|
Assert.Contains("Username=me", s);
|
||||||
Assert.Contains("Password=\"with;quote\"\"\"", s);
|
// Contains double-quote, no single-quote -> prefer single-quoted per DbConnectionStringBuilder-like behavior; parsed value contains one double-quote
|
||||||
Assert.Contains("Application Name=\"my app\"", s);
|
Assert.Contains("Password='with;quote" + '"' + "'", s);
|
||||||
|
Assert.Contains("Application Name=my app", s);
|
||||||
Assert.Contains("SSL Mode=Prefer", s);
|
Assert.Contains("SSL Mode=Prefer", s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,24 +236,44 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
|
||||||
private static string FormatPair(string key, string? value)
|
private static string FormatPair(string key, string? value)
|
||||||
{
|
{
|
||||||
value ??= string.Empty;
|
value ??= string.Empty;
|
||||||
var needsQuotes = NeedsQuoting(value);
|
// Decide if we need quoting following DbConnectionStringBuilder rules:
|
||||||
if (!needsQuotes) return key + "=" + value;
|
// - Empty => quote
|
||||||
return key + "=\"" + EscapeQuoted(value) + "\"";
|
// - Leading or trailing whitespace => quote
|
||||||
|
// - Contains ';' or '=' => quote
|
||||||
|
// - Otherwise, no quotes, even if it contains internal whitespace
|
||||||
|
if (!NeedsQuoting(value))
|
||||||
|
return key + "=" + value;
|
||||||
|
|
||||||
|
// Choose single or double quotes. Prefer the one not present in the value; if both present, pick double and escape.
|
||||||
|
bool hasSingle = value.Contains('\'');
|
||||||
|
bool hasDouble = value.Contains('"');
|
||||||
|
if (!hasSingle)
|
||||||
|
{
|
||||||
|
// Use single quotes, escape single quotes by doubling when needed (not needed here since !hasSingle)
|
||||||
|
return key + "='" + value.Replace("'", "''") + "'";
|
||||||
|
}
|
||||||
|
if (!hasDouble)
|
||||||
|
{
|
||||||
|
// Use double quotes
|
||||||
|
return key + "=\"" + value.Replace("\"", "\"\"") + "\"";
|
||||||
|
}
|
||||||
|
// Value contains both quote types: default to double quotes and escape doubles by doubling
|
||||||
|
return key + "=\"" + value.Replace("\"", "\"\"") + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool NeedsQuoting(string value)
|
private static bool NeedsQuoting(string value)
|
||||||
{
|
{
|
||||||
if (value.Length == 0) return true;
|
if (value.Length == 0) return true;
|
||||||
foreach (var c in value)
|
// Leading or trailing whitespace requires quoting
|
||||||
{
|
if (char.IsWhiteSpace(value[0]) || char.IsWhiteSpace(value[^1])) return true;
|
||||||
if (char.IsWhiteSpace(c) || c == ';' || c == '=' || c == '"') return true;
|
// Special characters
|
||||||
}
|
if (value.IndexOf(';') >= 0 || value.IndexOf('=') >= 0) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string EscapeQuoted(string value)
|
private static string EscapeQuoted(string value)
|
||||||
{
|
{
|
||||||
// Double the quotes per standard DbConnectionString rules
|
// Retained for compatibility, but not used directly; prefer inlined replacements in FormatPair
|
||||||
return value.Replace("\"", "\"\"");
|
return value.Replace("\"", "\"\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,19 +299,19 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
|
||||||
|
|
||||||
// read value
|
// read value
|
||||||
string value;
|
string value;
|
||||||
if (i < input.Length && input[i] == '"')
|
if (i < input.Length && (input[i] == '"' || input[i] == '\''))
|
||||||
{
|
{
|
||||||
i++; // skip opening quote
|
char quote = input[i++]; // opening quote (' or ")
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
while (i < input.Length)
|
while (i < input.Length)
|
||||||
{
|
{
|
||||||
char c = input[i++];
|
char c = input[i++];
|
||||||
if (c == '"')
|
if (c == quote)
|
||||||
{
|
{
|
||||||
if (i < input.Length && input[i] == '"')
|
if (i < input.Length && input[i] == quote)
|
||||||
{
|
{
|
||||||
// doubled quote -> literal quote
|
// doubled quote -> literal quote
|
||||||
sb.Append('"');
|
sb.Append(quote);
|
||||||
i++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -311,6 +331,7 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
|
||||||
{
|
{
|
||||||
int valStart = i;
|
int valStart = i;
|
||||||
while (i < input.Length && input[i] != ';') i++;
|
while (i < input.Length && input[i] != ';') i++;
|
||||||
|
// Unquoted value: per DbConnectionStringBuilder, leading/trailing whitespace is ignored
|
||||||
value = input.Substring(valStart, i - valStart).Trim();
|
value = input.Substring(valStart, i - valStart).Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue