Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Weasel.Postgresql.Tests/CommandExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,42 @@ public void add_first_parameter()
param.Value.ShouldBe("a");
param.ParameterName.ShouldBe("p0");

// AddParameter was given no explicit type, so this asserts *Npgsql's* inference from
// the value rather than anything Weasel stamped on. That inference reads Npgsql's
// process-global type mapper and answers Unknown until the mapper has been seeded,
// which TestSetup does once at module load. Do not delete that warm-up. weasel#398.
param.NpgsqlDbType.ShouldBe(NpgsqlDbType.Text);

command.Parameters.ShouldContain(param);
}

[Fact]
public void add_parameter_honors_an_explicit_type()
{
var command = new NpgsqlCommand();

var param = command.AddParameter("a", NpgsqlDbType.Varchar);

// The explicit-type path is the one Weasel actually controls, so unlike the test
// above it holds no matter what state Npgsql's global type mapper is in.
param.NpgsqlDbType.ShouldBe(NpgsqlDbType.Varchar);
}

[Fact]
public void add_parameter_without_a_type_defers_to_npgsql_rather_than_weasels_mapping()
{
var command = new NpgsqlCommand();

var param = command.AddParameter(new DateTime(2026, 7, 30, 12, 0, 0, DateTimeKind.Utc));

// Weasel must NOT start stamping its own CLR-type mapping onto untyped parameters.
// Weasel maps DateTime to "timestamp without time zone" for every value, while
// Npgsql resolves per value: a Kind=Utc DateTime is "timestamp with time zone", and
// writing one as "timestamp without time zone" throws at execution time. weasel#398.
param.NpgsqlDbType.ShouldBe(NpgsqlDbType.TimestampTz);
Instance.ToParameterType(typeof(DateTime)).ShouldBe(NpgsqlDbType.Timestamp);
}

[Fact]
public void add_second_parameter()
{
Expand Down
32 changes: 32 additions & 0 deletions src/Weasel.Postgresql.Tests/TestSetup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using Npgsql;

namespace Weasel.Postgresql.Tests;

Expand All @@ -23,5 +24,36 @@ internal static void Initialize()
{
PostgresqlProvider.Instance.UseCaseSensitiveQualifiedNames = useCaseSensitiveQualifiedNames;
}

WarmUpNpgsqlTypeInference();
}

/// <summary>
/// Populate Npgsql's process-global type mapper before any test runs.
/// </summary>
/// <remarks>
/// <para>
/// When a parameter is added without an explicit type, Weasel leaves
/// <see cref="NpgsqlParameter.NpgsqlDbType" /> unset and lets Npgsql infer it from the
/// value. That getter answers out of Npgsql's process-global type mapper, which stays
/// empty until the process constructs its first <see cref="NpgsqlDataSource" /> or
/// <see cref="NpgsqlConnection" /> with a connection string — until then it reports
/// <c>NpgsqlDbType.Unknown</c> for every value.
/// </para>
/// <para>
/// So a test asserting an inferred parameter type silently depends on some *other*
/// test in the same process having built a data source first. Under xUnit v3's parallel
/// collections that is a race, and it is exactly what made
/// <c>CommandExtensionsTests.add_first_parameter</c> fail in three of the four Postgres
/// CI jobs and then pass on an unchanged re-run. See weasel#398.
/// </para>
/// <para>
/// Constructing a builder is enough to seed the global mapper; it opens no sockets and
/// needs no connection string, so it is safe to do unconditionally at load.
/// </para>
/// </remarks>
private static void WarmUpNpgsqlTypeInference()
{
_ = new NpgsqlDataSourceBuilder();
}
}
Loading