From c5b3a36443807eb5af2ce6e02e580176d9f030cb Mon Sep 17 00:00:00 2001 From: Emilien Bevierre Date: Tue, 7 Oct 2025 11:10:10 +0200 Subject: [PATCH] Allow users to specify timespan unit in connection string parameters --- .../Internal/ConnectionString.cs | 68 ++++++++++++++++++- .../Internal/ConnectionStringTests.cs | 21 ++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/Couchbase.Analytics/Internal/ConnectionString.cs b/src/Couchbase.Analytics/Internal/ConnectionString.cs index b01cc07..ac03ec1 100644 --- a/src/Couchbase.Analytics/Internal/ConnectionString.cs +++ b/src/Couchbase.Analytics/Internal/ConnectionString.cs @@ -214,7 +214,73 @@ public bool TryGetParameter(string key, out TimeSpan parameter) { if (TryGetParameter(key, out string value)) { - parameter = TimeSpan.FromMilliseconds(Convert.ToUInt32(value)); + var trimmed = value.Trim(); + if (trimmed.Length == 0) + { + parameter = default; + return false; + } + + // Parse optional unit suffix: us, ms, s, m, h (case-insensitive) + // Default unit is milliseconds if no suffix is provided + var lower = trimmed.ToLowerInvariant(); + + // Determine unit and numeric portion (check longest suffixes first) + string unit; + string numericPortion; + if (lower.EndsWith("us")) + { + unit = "us"; + numericPortion = trimmed[..^2].Trim(); + } + else if (lower.EndsWith("ms")) + { + unit = "ms"; + numericPortion = trimmed[..^2].Trim(); + } + else if (lower.EndsWith("s")) + { + unit = "s"; + numericPortion = trimmed[..^1].Trim(); + } + else if (lower.EndsWith("m")) + { + unit = "m"; + numericPortion = trimmed[..^1].Trim(); + } + else if (lower.EndsWith("h")) + { + unit = "h"; + numericPortion = trimmed[..^1].Trim(); + } + else + { + unit = "ms"; + numericPortion = trimmed; + } + + if (numericPortion.Length == 0) + { + parameter = TimeSpan.Zero; + return false; + } + + // Allow integer or decimal values + if (!double.TryParse(numericPortion, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var amount)) + { + parameter = TimeSpan.Zero; + throw new FormatException($"Invalid numeric value for parameter '{key}': '{value}'"); + } + + parameter = unit switch + { + "us" => TimeSpan.FromMicroseconds(amount), + "ms" => TimeSpan.FromMilliseconds(amount), + "s" => TimeSpan.FromSeconds(amount), + "m" => TimeSpan.FromMinutes(amount), + "h" => TimeSpan.FromHours(amount), + _ => TimeSpan.FromMilliseconds(amount) + }; return true; } diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/ConnectionStringTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/ConnectionStringTests.cs index 7109520..9fd3b7d 100644 --- a/tests/Couchbase.Analytics.UnitTests/Internal/ConnectionStringTests.cs +++ b/tests/Couchbase.Analytics.UnitTests/Internal/ConnectionStringTests.cs @@ -301,6 +301,27 @@ public void Test_ConnectionString_TimeoutParameter_QueryTimeout_Values(string ti Assert.Equal(TimeSpan.FromMilliseconds(expectedMilliseconds), options.TimeoutOptions.QueryTimeout); } + [Theory] + [InlineData("500us", 5_000L)] // 500 microseconds -> 5000 ticks + [InlineData("250ms", 2_500_000L)] // 250 milliseconds -> 2,500,000 ticks + [InlineData("30s", 300_000_000L)] // 30 seconds -> 300,000,000 ticks + [InlineData("2m", 1_200_000_000L)] // 2 minutes -> 1,200,000,000 ticks + [InlineData("1h", 36_000_000_000L)] // 1 hour -> 36,000,000,000 ticks + [InlineData("30S", 300_000_000L)] // case-insensitive + [InlineData("1.5s", 15_000_000L)] // fractional seconds + [InlineData("0s", 0L)] // zero value + public void Test_ConnectionString_TimeoutParameter_ConnectTimeout_WithUnits(string timeoutValue, long expectedTicks) + { + var connectionString = $"http://localhost:8095?timeout.connect_timeout={timeoutValue}"; + + var options = new ClusterOptions + { + ConnectionString = connectionString + }; + + Assert.Equal(expectedTicks, options.TimeoutOptions.ConnectTimeout.Ticks); + } + [Theory] [InlineData("true", true)] [InlineData("false", false)]