connection string/url parsing and generation in the server configuration dialog
This commit is contained in:
parent
a5cb6ef7d4
commit
1d53ca2fc2
11 changed files with 410 additions and 62 deletions
|
|
@ -38,30 +38,42 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
|
|||
// Hosts and Ports
|
||||
if (dict.TryGetValue("Host", out var hostVal) || dict.TryGetValue("Server", out hostVal) || dict.TryGetValue("Servers", out hostVal))
|
||||
{
|
||||
var hosts = SplitList(hostVal).ToList();
|
||||
List<ushort?> portsPerHost = new();
|
||||
var rawHosts = SplitList(hostVal).ToList();
|
||||
var hosts = new List<string>(rawHosts.Count);
|
||||
var portsPerHost = new List<ushort?>(rawHosts.Count);
|
||||
|
||||
// First, extract inline ports from each host entry (e.g., host:5432 or [::1]:5432)
|
||||
foreach (var raw in rawHosts)
|
||||
{
|
||||
ParseHostPort(raw, out var hostOnly, out var inlinePort);
|
||||
hosts.Add(hostOnly);
|
||||
portsPerHost.Add(inlinePort);
|
||||
}
|
||||
|
||||
// Then, merge values from Port key: single port applies to all hosts missing a port;
|
||||
// list of ports applies 1:1 for hosts that still miss a port. Inline ports take precedence.
|
||||
if (dict.TryGetValue("Port", out var portVal))
|
||||
{
|
||||
var ports = SplitList(portVal).ToList();
|
||||
if (ports.Count == 1 && ushort.TryParse(ports[0], out var singlePort))
|
||||
if (ports.Count == 1 && ushort.TryParse(ports[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var singlePort))
|
||||
{
|
||||
foreach (var _ in hosts) portsPerHost.Add(singlePort);
|
||||
for (int i = 0; i < portsPerHost.Count; i++)
|
||||
if (!portsPerHost[i].HasValue)
|
||||
portsPerHost[i] = singlePort;
|
||||
}
|
||||
else if (ports.Count == hosts.Count)
|
||||
{
|
||||
foreach (var p in ports)
|
||||
for (int i = 0; i < ports.Count; i++)
|
||||
{
|
||||
if (ushort.TryParse(p, NumberStyles.Integer, CultureInfo.InvariantCulture, out var up))
|
||||
portsPerHost.Add(up);
|
||||
else
|
||||
portsPerHost.Add(null);
|
||||
if (!portsPerHost[i].HasValue && ushort.TryParse(ports[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out var up))
|
||||
portsPerHost[i] = up;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hosts.Count; i++)
|
||||
{
|
||||
ushort? port = i < portsPerHost.Count ? portsPerHost[i] : null;
|
||||
descriptor.AddHost(hosts[i], port);
|
||||
descriptor.AddHost(hosts[i], i < portsPerHost.Count ? portsPerHost[i] : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +176,42 @@ public sealed class NpgsqlCodec : IConnectionStringCodec
|
|||
return s.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
}
|
||||
|
||||
private static void ParseHostPort(string hostPart, out string host, out ushort? port)
|
||||
{
|
||||
host = hostPart;
|
||||
port = null;
|
||||
if (string.IsNullOrWhiteSpace(hostPart)) return;
|
||||
|
||||
// IPv6 in brackets: [::1]:5432
|
||||
if (hostPart[0] == '[')
|
||||
{
|
||||
int end = hostPart.IndexOf(']');
|
||||
if (end > 0)
|
||||
{
|
||||
host = hostPart.Substring(1, end - 1);
|
||||
if (end + 1 < hostPart.Length && hostPart[end + 1] == ':')
|
||||
{
|
||||
var ps = hostPart.Substring(end + 2);
|
||||
if (ushort.TryParse(ps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
|
||||
port = p;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-IPv6: split on last ':' and ensure right side is numeric
|
||||
int colon = hostPart.LastIndexOf(':');
|
||||
if (colon > 0 && colon < hostPart.Length - 1)
|
||||
{
|
||||
var ps = hostPart.Substring(colon + 1);
|
||||
if (ushort.TryParse(ps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
|
||||
{
|
||||
host = hostPart.Substring(0, colon);
|
||||
port = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetFirst(Dictionary<string, string> dict, out string value, params string[] keys)
|
||||
{
|
||||
foreach (var k in keys)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ public sealed class LibpqCodec : IConnectionStringCodec
|
|||
{
|
||||
try
|
||||
{
|
||||
// Reject Npgsql-style strings that use ';' separators when forcing libpq
|
||||
if (input.IndexOf(';') >= 0)
|
||||
return Result.Fail<ConnectionDescriptor>("Semicolons are not valid separators in libpq connection strings");
|
||||
var kv = new PqConnectionStringParser(new PqConnectionStringTokenizer(input)).Parse();
|
||||
|
||||
// libpq keywords are case-insensitive; normalize to lower for lookup
|
||||
|
|
|
|||
|
|
@ -67,13 +67,29 @@ public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
|
|||
{
|
||||
while (position < input.Length && char.IsWhiteSpace(input[position]))
|
||||
position++;
|
||||
// If a semicolon is encountered between pairs (which is not valid in libpq),
|
||||
// treat as immediate EOF so parser stops and leaves trailing data unparsed.
|
||||
if (position < input.Length && input[position] == ';')
|
||||
{
|
||||
position = input.Length; // force EOF
|
||||
}
|
||||
}
|
||||
|
||||
private string UnquotedString(bool forKeyword)
|
||||
{
|
||||
int start = position;
|
||||
while (++position < input.Length && !char.IsWhiteSpace(input[position]) && (!forKeyword || input[position] != '='))
|
||||
{ }
|
||||
while (++position < input.Length)
|
||||
{
|
||||
char c = input[position];
|
||||
// Libpq syntax does not use semicolons as pair separators; treat ';' as invalid here
|
||||
if (c == ';')
|
||||
{
|
||||
// Force tokenizer to stop and later cause a parse error by making GetValue/keyword incomplete
|
||||
break;
|
||||
}
|
||||
if (char.IsWhiteSpace(c)) break;
|
||||
if (forKeyword && c == '=') break;
|
||||
}
|
||||
return input.Substring(start, position - start);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue