diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fe542161a2..e300c68775 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,16 +25,16 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022]
- pg_major: [15, 14, 13, 12, 11, 10]
+ pg_major: [16, 15, 14, 13]
config: [Release]
test_tfm: [net7.0]
include:
- os: ubuntu-22.04
- pg_major: 15
+ pg_major: 16
config: Debug
test_tfm: net7.0
- os: ubuntu-22.04
- pg_major: 15
+ pg_major: 16
config: Release
test_tfm: netcoreapp3.1
- os: macos-12
@@ -80,6 +80,9 @@ jobs:
# First uninstall any PostgreSQL installed on the image
dpkg-query -W --showformat='${Package}\n' 'postgresql-*' | xargs sudo dpkg -P postgresql
+ # Import the repository signing key
+ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main ${{ matrix.pg_major }}" >> /etc/apt/sources.list.d/pgdg.list'
sudo apt-get update -qq
sudo apt-get install -qq postgresql-${{ matrix.pg_major }}
@@ -348,7 +351,7 @@ jobs:
dotnet-version: ${{ env.dotnet_sdk_version }}
- name: Pack
- run: dotnet pack Npgsql.sln --configuration Release --output nupkgs --version-suffix "ci.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}" -p:ContinuousIntegrationBuild=true
+ run: dotnet pack Npgsql.sln --configuration Release --property:PackageOutputPath="$PWD/nupkgs" --version-suffix "ci.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}" -p:ContinuousIntegrationBuild=true
- name: Upload artifacts (nupkg)
uses: actions/upload-artifact@v3
@@ -382,7 +385,7 @@ jobs:
dotnet-version: ${{ env.dotnet_sdk_version }}
- name: Pack
- run: dotnet pack --configuration Release --output nupkgs -p:ContinuousIntegrationBuild=true
+ run: dotnet pack Npgsql.sln --configuration Release --property:PackageOutputPath="$PWD/nupkgs" -p:ContinuousIntegrationBuild=true
- name: Upload artifacts
uses: actions/upload-artifact@v3
diff --git a/.github/workflows/rich-code-nav.yml b/.github/workflows/rich-code-nav.yml
index 118c2e3e28..7b1c7588ae 100644
--- a/.github/workflows/rich-code-nav.yml
+++ b/.github/workflows/rich-code-nav.yml
@@ -14,6 +14,7 @@ env:
jobs:
build:
+ if: ${{ false }} # disable as it's failing, see https://github.com/microsoft/RichCodeNavIndexer/issues/128
runs-on: windows-latest
steps:
diff --git a/Directory.Build.props b/Directory.Build.props
index 6d7a7869ab..5947fbb233 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- 7.0.0
+ 7.0.10
latest
true
enable
@@ -10,7 +10,7 @@
true
true
- Copyright 2022 © The Npgsql Development Team
+ Copyright 2023 © The Npgsql Development Team
Npgsql
PostgreSQL
https://github.com/npgsql/npgsql
@@ -19,7 +19,7 @@
true
snupkg
true
- $(NoWarn);NETSDK1138
+ $(NoWarn);NETSDK1138;CA2017;CA2022
true
@@ -33,4 +33,8 @@
+
+
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1eed6bee0c..7587a1cf86 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -39,7 +39,7 @@
-
+
diff --git a/Npgsql.sln.DotSettings b/Npgsql.sln.DotSettings
index 69c98554d3..984ce08eda 100644
--- a/Npgsql.sln.DotSettings
+++ b/Npgsql.sln.DotSettings
@@ -121,6 +121,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/Npgsql.DependencyInjection/NpgsqlServiceCollectionExtensions.cs b/src/Npgsql.DependencyInjection/NpgsqlServiceCollectionExtensions.cs
index a9333a0753..dd73e7c14e 100644
--- a/src/Npgsql.DependencyInjection/NpgsqlServiceCollectionExtensions.cs
+++ b/src/Npgsql.DependencyInjection/NpgsqlServiceCollectionExtensions.cs
@@ -22,7 +22,7 @@ public static class NpgsqlServiceCollectionExtensions
///
///
/// The lifetime with which to register the in the container.
- /// Defaults to .
+ /// Defaults to .
///
///
/// The lifetime with which to register the service in the container.
@@ -44,7 +44,7 @@ public static IServiceCollection AddNpgsqlDataSource(
/// An Npgsql connection string.
///
/// The lifetime with which to register the in the container.
- /// Defaults to .
+ /// Defaults to .
///
///
/// The lifetime with which to register the service in the container.
@@ -69,7 +69,7 @@ public static IServiceCollection AddNpgsqlDataSource(
///
///
/// The lifetime with which to register the in the container.
- /// Defaults to .
+ /// Defaults to .
///
///
/// The lifetime with which to register the service in the container.
@@ -93,7 +93,7 @@ public static IServiceCollection AddMultiHostNpgsqlDataSource(
/// An Npgsql connection string.
///
/// The lifetime with which to register the in the container.
- /// Defaults to .
+ /// Defaults to .
///
///
/// The lifetime with which to register the service in the container.
diff --git a/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs b/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs
index 4c3c90b866..ba040ed79d 100644
--- a/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs
+++ b/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs
@@ -167,6 +167,7 @@ async ValueTask ReadGeometryCore(NpgsqlReadBuffer buf, bool async
var lines = new LineString[buf.ReadInt32(littleEndian)];
for (var i = 0; i < lines.Length; ++i)
{
+ await buf.Ensure(SizeOfLength, async);
var coordinates = new Position[buf.ReadInt32(littleEndian)];
for (var j = 0; j < coordinates.Length; ++j)
{
@@ -230,6 +231,7 @@ async ValueTask ReadGeometryCore(NpgsqlReadBuffer buf, bool async
var lines = new LineString[buf.ReadInt32(littleEndian)];
for (var j = 0; j < lines.Length; ++j)
{
+ await buf.Ensure(SizeOfLength, async);
var coordinates = new Position[buf.ReadInt32(littleEndian)];
for (var k = 0; k < coordinates.Length; ++k)
{
@@ -449,7 +451,7 @@ public async Task Write(Point value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? l
public async Task Write(LineString value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default)
{
var type = EwkbGeometryType.LineString;
- var size = SizeOfHeader;
+ var size = SizeOfHeaderWithLength;
var srid = GetSrid(value.CRS);
if (srid != 0)
{
@@ -476,7 +478,7 @@ public async Task Write(LineString value, NpgsqlWriteBuffer buf, NpgsqlLengthCac
public async Task Write(Polygon value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default)
{
var type = EwkbGeometryType.Polygon;
- var size = SizeOfHeader;
+ var size = SizeOfHeaderWithLength;
var srid = GetSrid(value.CRS);
if (srid != 0)
{
@@ -498,7 +500,7 @@ public async Task Write(Polygon value, NpgsqlWriteBuffer buf, NpgsqlLengthCache?
for (var i = 0; i < lines.Count; ++i)
{
- if (buf.WriteSpaceLeft < 4)
+ if (buf.WriteSpaceLeft < SizeOfLength)
await buf.Flush(async, cancellationToken);
var coordinates = lines[i].Coordinates;
buf.WriteInt32(coordinates.Count);
@@ -510,7 +512,7 @@ public async Task Write(Polygon value, NpgsqlWriteBuffer buf, NpgsqlLengthCache?
public async Task Write(MultiPoint value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default)
{
var type = EwkbGeometryType.MultiPoint;
- var size = SizeOfHeader;
+ var size = SizeOfHeaderWithLength;
var srid = GetSrid(value.CRS);
if (srid != 0)
{
@@ -537,7 +539,7 @@ public async Task Write(MultiPoint value, NpgsqlWriteBuffer buf, NpgsqlLengthCac
public async Task Write(MultiLineString value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default)
{
var type = EwkbGeometryType.MultiLineString;
- var size = SizeOfHeader;
+ var size = SizeOfHeaderWithLength;
var srid = GetSrid(value.CRS);
if (srid != 0)
{
@@ -564,7 +566,7 @@ public async Task Write(MultiLineString value, NpgsqlWriteBuffer buf, NpgsqlLeng
public async Task Write(MultiPolygon value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default)
{
var type = EwkbGeometryType.MultiPolygon;
- var size = SizeOfHeader;
+ var size = SizeOfHeaderWithLength;
var srid = GetSrid(value.CRS);
if (srid != 0)
{
@@ -590,7 +592,7 @@ public async Task Write(MultiPolygon value, NpgsqlWriteBuffer buf, NpgsqlLengthC
public async Task Write(GeometryCollection value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? lengthCache, NpgsqlParameter? parameter, bool async, CancellationToken cancellationToken = default)
{
var type = EwkbGeometryType.GeometryCollection;
- var size = SizeOfHeader;
+ var size = SizeOfHeaderWithLength;
var srid = GetSrid(value.CRS);
if (srid != 0)
{
@@ -717,4 +719,4 @@ enum EwkbGeometryType : uint
HasSrid = 0x20000000,
HasM = 0x40000000,
HasZ = 0x80000000
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql.GeoJSON/PublicAPI.Shipped.txt b/src/Npgsql.GeoJSON/PublicAPI.Shipped.txt
index 13ced34dc9..a5e3b621d4 100644
--- a/src/Npgsql.GeoJSON/PublicAPI.Shipped.txt
+++ b/src/Npgsql.GeoJSON/PublicAPI.Shipped.txt
@@ -5,4 +5,4 @@ Npgsql.GeoJSONOptions.LongCRS = 4 -> Npgsql.GeoJSONOptions
Npgsql.GeoJSONOptions.None = 0 -> Npgsql.GeoJSONOptions
Npgsql.GeoJSONOptions.ShortCRS = 2 -> Npgsql.GeoJSONOptions
Npgsql.NpgsqlGeoJSONExtensions
-static Npgsql.NpgsqlGeoJSONExtensions.UseGeoJson(this Npgsql.TypeMapping.INpgsqlTypeMapper! mapper, Npgsql.GeoJSONOptions options = Npgsql.GeoJSONOptions.None, bool geographyAsDefault = false) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
\ No newline at end of file
+static Npgsql.NpgsqlGeoJSONExtensions.UseGeoJson(this Npgsql.TypeMapping.INpgsqlTypeMapper! mapper, Npgsql.GeoJSONOptions options = Npgsql.GeoJSONOptions.None, bool geographyAsDefault = false) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
diff --git a/src/Npgsql.Json.NET/PublicAPI.Shipped.txt b/src/Npgsql.Json.NET/PublicAPI.Shipped.txt
index 48ddf42ce5..dd615d73a6 100644
--- a/src/Npgsql.Json.NET/PublicAPI.Shipped.txt
+++ b/src/Npgsql.Json.NET/PublicAPI.Shipped.txt
@@ -1,3 +1,3 @@
#nullable enable
Npgsql.NpgsqlJsonNetExtensions
-static Npgsql.NpgsqlJsonNetExtensions.UseJsonNet(this Npgsql.TypeMapping.INpgsqlTypeMapper! mapper, System.Type![]? jsonbClrTypes = null, System.Type![]? jsonClrTypes = null, Newtonsoft.Json.JsonSerializerSettings? settings = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
\ No newline at end of file
+static Npgsql.NpgsqlJsonNetExtensions.UseJsonNet(this Npgsql.TypeMapping.INpgsqlTypeMapper! mapper, System.Type![]? jsonbClrTypes = null, System.Type![]? jsonClrTypes = null, Newtonsoft.Json.JsonSerializerSettings? settings = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
diff --git a/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteHandler.cs b/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteHandler.cs
index b1cb3783e1..f75be9f4a7 100644
--- a/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteHandler.cs
+++ b/src/Npgsql.NetTopologySuite/Internal/NetTopologySuiteHandler.cs
@@ -22,7 +22,6 @@ partial class NetTopologySuiteHandler : NpgsqlTypeHandler,
{
readonly PostGisReader _reader;
readonly PostGisWriter _writer;
- readonly LengthStream _lengthStream = new();
internal NetTopologySuiteHandler(PostgresType postgresType, PostGisReader reader, PostGisWriter writer)
: base(postgresType)
@@ -91,9 +90,10 @@ int INpgsqlTypeHandler.ValidateAndGetLength(GeometryCollecti
int ValidateAndGetLengthCore(Geometry value)
{
- _lengthStream.SetLength(0);
- _writer.Write(value, _lengthStream);
- return (int)_lengthStream.Length;
+ var lengthStream = new LengthStream();
+ lengthStream.SetLength(0);
+ _writer.Write(value, lengthStream);
+ return (int)lengthStream.Length;
}
sealed class LengthStream : Stream
diff --git a/src/Npgsql.NetTopologySuite/PublicAPI.Unshipped.txt b/src/Npgsql.NetTopologySuite/PublicAPI.Unshipped.txt
index 5f282702bb..ab058de62d 100644
--- a/src/Npgsql.NetTopologySuite/PublicAPI.Unshipped.txt
+++ b/src/Npgsql.NetTopologySuite/PublicAPI.Unshipped.txt
@@ -1 +1 @@
-
\ No newline at end of file
+#nullable enable
diff --git a/src/Npgsql.NodaTime/Internal/IntervalHandler.cs b/src/Npgsql.NodaTime/Internal/IntervalHandler.cs
index 4e9305a20b..f0e9487409 100644
--- a/src/Npgsql.NodaTime/Internal/IntervalHandler.cs
+++ b/src/Npgsql.NodaTime/Internal/IntervalHandler.cs
@@ -46,6 +46,8 @@ public override int ValidateAndGetLength(Period value, NpgsqlParameter? paramete
public override void Write(Period value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter)
{
+ // We have to normalize the value as otherwise we might get a value with 0 everything except for ticks, which we ignore
+ value = value.Normalize();
// Note that the end result must be long
// see #3438
var microsecondsInDay =
@@ -103,4 +105,4 @@ int INpgsqlSimpleTypeHandler.ValidateAndGetLength(NpgsqlInterval
void INpgsqlSimpleTypeHandler.Write(NpgsqlInterval value, NpgsqlWriteBuffer buf, NpgsqlParameter? parameter)
=> ((INpgsqlSimpleTypeHandler)_bclHandler).Write(value, buf, parameter);
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/BackendMessages/CommandCompleteMessage.cs b/src/Npgsql/BackendMessages/CommandCompleteMessage.cs
index 6d9800a27f..63080052cb 100644
--- a/src/Npgsql/BackendMessages/CommandCompleteMessage.cs
+++ b/src/Npgsql/BackendMessages/CommandCompleteMessage.cs
@@ -111,7 +111,7 @@ static bool AreEqual(byte[] bytes, int pos, string s)
static ulong ParseNumber(byte[] bytes, ref int pos)
{
Debug.Assert(bytes[pos] >= '0' && bytes[pos] <= '9');
- uint result = 0;
+ ulong result = 0;
do
{
result = result * 10 + bytes[pos++] - '0';
diff --git a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs
index 02a8890dde..6c4e7dd3cd 100644
--- a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs
+++ b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs
@@ -19,42 +19,45 @@ partial class NpgsqlConnector
{
async Task Authenticate(string username, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)
{
- timeout.CheckAndApply(this);
- var msg = ExpectAny(await ReadMessage(async), this);
- switch (msg.AuthRequestType)
+ while (true)
{
- case AuthenticationRequestType.AuthenticationOk:
- return;
+ timeout.CheckAndApply(this);
+ var msg = ExpectAny(await ReadMessage(async), this);
+ switch (msg.AuthRequestType)
+ {
+ case AuthenticationRequestType.AuthenticationOk:
+ return;
- case AuthenticationRequestType.AuthenticationCleartextPassword:
- await AuthenticateCleartext(username, async, cancellationToken);
- return;
+ case AuthenticationRequestType.AuthenticationCleartextPassword:
+ await AuthenticateCleartext(username, async, cancellationToken);
+ break;
- case AuthenticationRequestType.AuthenticationMD5Password:
- await AuthenticateMD5(username, ((AuthenticationMD5PasswordMessage)msg).Salt, async, cancellationToken);
- return;
+ case AuthenticationRequestType.AuthenticationMD5Password:
+ await AuthenticateMD5(username, ((AuthenticationMD5PasswordMessage)msg).Salt, async, cancellationToken);
+ break;
- case AuthenticationRequestType.AuthenticationSASL:
- await AuthenticateSASL(((AuthenticationSASLMessage)msg).Mechanisms, username, async, cancellationToken);
- return;
+ case AuthenticationRequestType.AuthenticationSASL:
+ await AuthenticateSASL(((AuthenticationSASLMessage)msg).Mechanisms, username, async, cancellationToken);
+ break;
- case AuthenticationRequestType.AuthenticationGSS:
- case AuthenticationRequestType.AuthenticationSSPI:
- await AuthenticateGSS(async);
- return;
+ case AuthenticationRequestType.AuthenticationGSS:
+ case AuthenticationRequestType.AuthenticationSSPI:
+ await AuthenticateGSS(async);
+ return;
- case AuthenticationRequestType.AuthenticationGSSContinue:
- throw new NpgsqlException("Can't start auth cycle with AuthenticationGSSContinue");
+ case AuthenticationRequestType.AuthenticationGSSContinue:
+ throw new NpgsqlException("Can't start auth cycle with AuthenticationGSSContinue");
- default:
- throw new NotSupportedException($"Authentication method not supported (Received: {msg.AuthRequestType})");
+ default:
+ throw new NotSupportedException($"Authentication method not supported (Received: {msg.AuthRequestType})");
+ }
}
}
async Task AuthenticateCleartext(string username, bool async, CancellationToken cancellationToken = default)
{
var passwd = await GetPassword(username, async, cancellationToken);
- if (passwd == null)
+ if (string.IsNullOrEmpty(passwd))
throw new NpgsqlException("No password has been provided but the backend requires one (in cleartext)");
var encoded = new byte[Encoding.UTF8.GetByteCount(passwd) + 1];
@@ -62,7 +65,6 @@ async Task AuthenticateCleartext(string username, bool async, CancellationToken
await WritePassword(encoded, async, cancellationToken);
await Flush(async, cancellationToken);
- ExpectAny(await ReadMessage(async), this);
}
async Task AuthenticateSASL(List mechanisms, string username, bool async, CancellationToken cancellationToken = default)
@@ -154,8 +156,9 @@ async Task AuthenticateSASL(List mechanisms, string username, bool async
throw new NpgsqlException("Unable to bind to SCRAM-SHA-256-PLUS, check logs for more information");
}
- var passwd = await GetPassword(username, async, cancellationToken) ??
- throw new NpgsqlException($"No password has been provided but the backend requires one (in SASL/{mechanism})");
+ var passwd = await GetPassword(username, async, cancellationToken);
+ if (string.IsNullOrEmpty(passwd))
+ throw new NpgsqlException($"No password has been provided but the backend requires one (in SASL/{mechanism})");
// Assumption: the write buffer is big enough to contain all our outgoing messages
var clientNonce = GetNonce();
@@ -204,10 +207,6 @@ async Task AuthenticateSASL(List mechanisms, string username, bool async
if (scramFinalServerMsg.ServerSignature != Convert.ToBase64String(serverSignature))
throw new NpgsqlException("[SCRAM] Unable to verify server signature");
- var okMsg = ExpectAny(await ReadMessage(async), this);
- if (okMsg.AuthRequestType != AuthenticationRequestType.AuthenticationOk)
- throw new NpgsqlException("[SASL] Expected AuthenticationOK message");
-
static string GetNonce()
{
@@ -240,7 +239,7 @@ static byte[] HMAC(byte[] data, string key)
async Task AuthenticateMD5(string username, byte[] salt, bool async, CancellationToken cancellationToken = default)
{
var passwd = await GetPassword(username, async, cancellationToken);
- if (passwd == null)
+ if (string.IsNullOrEmpty(passwd))
throw new NpgsqlException("No password has been provided but the backend requires one (in MD5)");
byte[] result;
@@ -281,7 +280,6 @@ async Task AuthenticateMD5(string username, byte[] salt, bool async, Cancellatio
await WritePassword(result, async, cancellationToken);
await Flush(async, cancellationToken);
- ExpectAny(await ReadMessage(async), this);
}
#if NET7_0_OR_GREATER
@@ -302,13 +300,15 @@ async Task AuthenticateGSS(bool async)
var response = ExpectAny(await ReadMessage(async), this);
if (response.AuthRequestType == AuthenticationRequestType.AuthenticationOk)
break;
- var gssMsg = response as AuthenticationGSSContinueMessage;
- if (gssMsg == null)
+ if (response is not AuthenticationGSSContinueMessage gssMsg)
throw new NpgsqlException($"Received unexpected authentication request message {response.AuthRequestType}");
data = authContext.GetOutgoingBlob(gssMsg.AuthenticationData.AsSpan(), out statusCode)!;
- if (statusCode == NegotiateAuthenticationStatusCode.Completed)
+ if (statusCode is not NegotiateAuthenticationStatusCode.Completed and not NegotiateAuthenticationStatusCode.ContinueNeeded)
+ throw new NpgsqlException($"Error while authenticating GSS/SSPI: {statusCode}");
+ // We might get NegotiateAuthenticationStatusCode.Completed but the data will not be null
+ // This can happen if it's the first cycle, in which case we have to send that data to complete handshake (#4888)
+ if (data is null)
continue;
- Debug.Assert(statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded);
await WritePassword(data, 0, data.Length, async, UserCancellationToken);
await Flush(async, UserCancellationToken);
}
diff --git a/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs b/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs
index c61f7b48bd..3e3576ead3 100644
--- a/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs
+++ b/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs
@@ -20,6 +20,7 @@ internal Task WriteDescribe(StatementOrPortal statementOrPortal, string name, bo
sizeof(byte) + // Statement or portal
(name.Length + 1); // Statement/portal name
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
return FlushAndWrite(len, statementOrPortal, name, async, cancellationToken);
@@ -47,6 +48,7 @@ internal Task WriteSync(bool async, CancellationToken cancellationToken = defaul
const int len = sizeof(byte) + // Message code
sizeof(int); // Length
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
return FlushAndWrite(async, cancellationToken);
@@ -76,6 +78,7 @@ internal Task WriteExecute(int maxRows, bool async, CancellationToken cancellati
sizeof(byte) + // Null-terminated portal name (always empty for now)
sizeof(int); // Max number of rows
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
return FlushAndWrite(maxRows, async, cancellationToken);
@@ -113,9 +116,6 @@ internal async Task WriteParse(string sql, string statementName, List= headerLength, "Write buffer too small for Bind header");
- await Flush(async, cancellationToken);
- }
-
var formatCodesSum = 0;
var paramsLength = 0;
for (var paramIndex = 0; paramIndex < parameters.Count; paramIndex++)
@@ -190,6 +188,13 @@ internal async Task WriteBind(
sizeof(short) + // Number of result format codes
sizeof(short) * (unknownResultTypeList?.Length ?? 1); // Result format codes
+ WriteBuffer.StartMessage(messageLength);
+ if (WriteBuffer.WriteSpaceLeft < headerLength)
+ {
+ Debug.Assert(WriteBuffer.Size >= headerLength, "Write buffer too small for Bind header");
+ await Flush(async, cancellationToken);
+ }
+
WriteBuffer.WriteByte(FrontendMessageCode.Bind);
WriteBuffer.WriteInt32(messageLength - 1);
Debug.Assert(portal == string.Empty);
@@ -251,7 +256,8 @@ internal Task WriteClose(StatementOrPortal type, string name, bool async, Cancel
sizeof(byte) + // Statement or portal
name.Length + sizeof(byte); // Statement or portal name plus null terminator
- if (WriteBuffer.WriteSpaceLeft < 10)
+ WriteBuffer.StartMessage(len);
+ if (WriteBuffer.WriteSpaceLeft < len)
return FlushAndWrite(len, type, name, async, cancellationToken);
Write(len, type, name);
@@ -279,14 +285,17 @@ internal async Task WriteQuery(string sql, bool async, CancellationToken cancell
{
var queryByteLen = TextEncoding.GetByteCount(sql);
+ var len = sizeof(byte) +
+ sizeof(int) + // Message length (including self excluding code)
+ queryByteLen + // Query byte length
+ sizeof(byte);
+
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < 1 + 4)
await Flush(async, cancellationToken);
WriteBuffer.WriteByte(FrontendMessageCode.Query);
- WriteBuffer.WriteInt32(
- sizeof(int) + // Message length (including self excluding code)
- queryByteLen + // Query byte length
- sizeof(byte)); // Null terminator
+ WriteBuffer.WriteInt32(len - 1);
await WriteBuffer.WriteString(sql, queryByteLen, async, cancellationToken);
if (WriteBuffer.WriteSpaceLeft < 1)
@@ -301,6 +310,7 @@ internal async Task WriteCopyDone(bool async, CancellationToken cancellationToke
const int len = sizeof(byte) + // Message code
sizeof(int); // Length
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
await Flush(async, cancellationToken);
@@ -316,6 +326,7 @@ internal async Task WriteCopyFail(bool async, CancellationToken cancellationToke
sizeof(int) + // Length
sizeof(byte); // Error message is always empty (only a null terminator)
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
await Flush(async, cancellationToken);
@@ -333,6 +344,7 @@ internal void WriteCancelRequest(int backendProcessId, int backendSecretKey)
Debug.Assert(backendProcessId != 0);
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
Flush(false).GetAwaiter().GetResult();
@@ -347,6 +359,7 @@ internal void WriteTerminate()
const int len = sizeof(byte) + // Message code
sizeof(int); // Length
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
Flush(false).GetAwaiter().GetResult();
@@ -359,6 +372,7 @@ internal void WriteSslRequest()
const int len = sizeof(int) + // Length
sizeof(int); // SSL request code
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
Flush(false).GetAwaiter().GetResult();
@@ -379,6 +393,7 @@ internal void WriteStartup(Dictionary parameters)
PGUtil.UTF8Encoding.GetByteCount(kvp.Value) + 1;
// Should really never happen, just in case
+ WriteBuffer.StartMessage(len);
if (len > WriteBuffer.Size)
throw new Exception("Startup message bigger than buffer");
@@ -402,8 +417,10 @@ internal void WriteStartup(Dictionary parameters)
internal async Task WritePassword(byte[] payload, int offset, int count, bool async, CancellationToken cancellationToken = default)
{
+ WriteBuffer.StartMessage(sizeof(byte) + sizeof(int) + count);
if (WriteBuffer.WriteSpaceLeft < sizeof(byte) + sizeof(int))
await WriteBuffer.Flush(async, cancellationToken);
+
WriteBuffer.WriteByte(FrontendMessageCode.Password);
WriteBuffer.WriteInt32(sizeof(int) + count);
@@ -426,6 +443,7 @@ internal async Task WriteSASLInitialResponse(string mechanism, byte[] initialRes
sizeof(int) + // Initial response length
(initialResponse?.Length ?? 0); // Initial response payload
+ WriteBuffer.StartMessage(len);
if (WriteBuffer.WriteSpaceLeft < len)
await WriteBuffer.Flush(async, cancellationToken);
@@ -449,6 +467,7 @@ internal async Task WriteSASLInitialResponse(string mechanism, byte[] initialRes
internal Task WritePregenerated(byte[] data, bool async = false, CancellationToken cancellationToken = default)
{
+ WriteBuffer.StartMessage(data.Length);
if (WriteBuffer.WriteSpaceLeft < data.Length)
return FlushAndWrite(data, async, cancellationToken);
@@ -466,4 +485,4 @@ async Task FlushAndWrite(byte[] data, bool async, CancellationToken cancellation
internal void Flush() => WriteBuffer.Flush(false).GetAwaiter().GetResult();
internal Task Flush(bool async, CancellationToken cancellationToken = default) => WriteBuffer.Flush(async, cancellationToken);
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/Internal/NpgsqlConnector.NetStandard.cs b/src/Npgsql/Internal/NpgsqlConnector.NetStandard.cs
new file mode 100644
index 0000000000..c6ec223fe8
--- /dev/null
+++ b/src/Npgsql/Internal/NpgsqlConnector.NetStandard.cs
@@ -0,0 +1,54 @@
+#if NETSTANDARD2_0
+
+using System.Net;
+using System.Reflection;
+
+namespace Npgsql.Internal;
+
+public partial class NpgsqlConnector
+{
+ static readonly object disableSystemDefaultTlsVersionsLock = new();
+
+ // volatile shouldn't be necessary since lock guarantees acquire/release semantics, but just in case
+ static volatile bool disableSystemDefaultTlsVersionsChecked;
+ static bool disableSystemDefaultTlsVersions;
+
+ static bool DisableSystemDefaultTlsVersions
+ {
+ get
+ {
+ if (!disableSystemDefaultTlsVersionsChecked)
+ {
+ lock (disableSystemDefaultTlsVersionsLock)
+ {
+ if (!disableSystemDefaultTlsVersionsChecked)
+ {
+ try
+ {
+ var spmType = typeof(ServicePointManager);
+ var disableDefaultProperty = spmType.GetProperty("DisableSystemDefaultTlsVersions", BindingFlags.Static | BindingFlags.NonPublic);
+ if (disableDefaultProperty is not null)
+ {
+ disableSystemDefaultTlsVersions = (bool)disableDefaultProperty.GetValue(null);
+ }
+ else
+ {
+ disableSystemDefaultTlsVersions = true;
+ }
+ }
+ catch
+ {
+ disableSystemDefaultTlsVersions = true;
+ }
+
+ disableSystemDefaultTlsVersionsChecked = true;
+ }
+ }
+ }
+
+ return disableSystemDefaultTlsVersions;
+ }
+ }
+}
+
+#endif
diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs
index cd1ef203bb..83d6b01473 100644
--- a/src/Npgsql/Internal/NpgsqlConnector.cs
+++ b/src/Npgsql/Internal/NpgsqlConnector.cs
@@ -96,6 +96,17 @@ public sealed partial class NpgsqlConnector : IDisposable
///
internal int BackendProcessId { get; private set; }
+ string? _inferredUserName;
+
+ ///
+ /// The user name that has been inferred when the connector was opened
+ ///
+ internal string InferredUserName
+ {
+ get => _inferredUserName ?? throw new InvalidOperationException($"{nameof(InferredUserName)} cannot be accessed before the connector has been opened.");
+ private set => _inferredUserName = value;
+ }
+
bool SupportsPostgresCancellation => BackendProcessId != 0;
///
@@ -136,6 +147,13 @@ public sealed partial class NpgsqlConnector : IDisposable
///
internal int PendingPrependedResponses { get; set; }
+ ///
+ /// A ManualResetEventSlim used to make sure a cancellation request doesn't run
+ /// while we're reading responses for the prepended query
+ /// as we can't gracefully handle their cancellation.
+ ///
+ readonly ManualResetEventSlim ReadingPrependedMessagesMRE = new(initialState: true);
+
internal NpgsqlDataReader? CurrentReader;
internal PreparedStatementManager PreparedStatementManager { get; }
@@ -207,7 +225,20 @@ internal void FlagAsWritableForMultiplexing()
/// cancellation is delivered. This reduces the chance that a cancellation meant for a previous
/// command will accidentally cancel a later one, see #615.
///
- internal object CancelLock { get; }
+ object CancelLock { get; } = new();
+
+ ///
+ /// A lock that's taken to make sure no other concurrent operation is running.
+ /// Break takes it to set the state of the connector.
+ /// Anyone else should immediately check the state and exit
+ /// if the connector is closed.
+ ///
+ object SyncObj { get; } = new();
+
+ ///
+ /// A lock that's used to wait for the Cleanup to complete while breaking the connection.
+ ///
+ object CleanupLock { get; } = new();
readonly bool _isKeepAliveEnabled;
readonly Timer? _keepAliveTimer;
@@ -251,6 +282,8 @@ internal bool PostgresCancellationPerformed
internal bool AttemptPostgresCancellation { get; private set; }
static readonly TimeSpan _cancelImmediatelyTimeout = TimeSpan.FromMilliseconds(-1);
+ X509Certificate2? _certificate;
+
internal NpgsqlLoggingConfiguration LoggingConfiguration { get; }
internal ILogger ConnectionLogger { get; }
@@ -342,12 +375,10 @@ internal NpgsqlConnector(NpgsqlDataSource dataSource, NpgsqlConnection conn)
Settings = dataSource.Settings;
PostgresParameters = new Dictionary();
- CancelLock = new object();
-
_isKeepAliveEnabled = Settings.KeepAlive > 0;
if (_isKeepAliveEnabled)
_keepAliveTimer = new Timer(PerformKeepAlive, null, Timeout.Infinite, Timeout.Infinite);
-
+
DataReader = new NpgsqlDataReader(this);
// TODO: Not just for automatic preparation anymore...
@@ -501,7 +532,7 @@ internal async Task Open(NpgsqlTimeout timeout, bool async, CancellationToken ca
// Start the keep alive mechanism to work by scheduling the timer.
// Otherwise, it doesn't work for cases when no query executed during
// the connection lifetime in case of a new connector.
- lock (this)
+ lock (SyncObj)
{
var keepAlive = Settings.KeepAlive * 1000;
_keepAliveTimer!.Change(keepAlive, keepAlive);
@@ -606,10 +637,12 @@ await OpenCore(
internal async ValueTask QueryDatabaseState(
NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken = default)
{
- using var cmd = CreateCommand("select pg_is_in_recovery(); SHOW default_transaction_read_only");
- cmd.CommandTimeout = (int)timeout.CheckAndGetTimeLeft().TotalSeconds;
+ using var batch = CreateBatch();
+ batch.BatchCommands.Add(new NpgsqlBatchCommand("select pg_is_in_recovery()"));
+ batch.BatchCommands.Add(new NpgsqlBatchCommand("SHOW default_transaction_read_only"));
+ batch.Timeout = (int)timeout.CheckAndGetTimeLeft().TotalSeconds;
- var reader = async ? await cmd.ExecuteReaderAsync(cancellationToken) : cmd.ExecuteReader();
+ var reader = async ? await batch.ExecuteReaderAsync(cancellationToken) : batch.ExecuteReader();
try
{
if (async)
@@ -626,7 +659,7 @@ internal async ValueTask QueryDatabaseState(
reader.NextResult();
reader.Read();
}
-
+
_isTransactionReadOnly = reader.GetString(0) != "off";
var databaseState = UpdateDatabaseState();
@@ -682,34 +715,51 @@ void WriteStartupMessage(string username)
WriteStartup(startupParams);
}
- async ValueTask GetUsernameAsync(bool async, CancellationToken cancellationToken)
+ ValueTask GetUsernameAsync(bool async, CancellationToken cancellationToken)
{
var username = Settings.Username;
if (username?.Length > 0)
- return username;
+ {
+ InferredUserName = username;
+ return new(username);
+ }
username = PostgresEnvironment.User;
if (username?.Length > 0)
- return username;
+ {
+ InferredUserName = username;
+ return new(username);
+ }
+
+ return GetUsernameAsyncInternal();
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ async ValueTask GetUsernameAsyncInternal()
{
- username = await KerberosUsernameProvider.GetUsernameAsync(Settings.IncludeRealm, ConnectionLogger, async, cancellationToken);
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ username = await KerberosUsernameProvider.GetUsernameAsync(Settings.IncludeRealm, ConnectionLogger, async,
+ cancellationToken);
+
+ if (username?.Length > 0)
+ {
+ InferredUserName = username;
+ return username;
+ }
+ }
+ username = Environment.UserName;
if (username?.Length > 0)
+ {
+ InferredUserName = username;
return username;
- }
-
- username = Environment.UserName;
- if (username?.Length > 0)
- return username;
+ }
- throw new NpgsqlException("No username could be found, please specify one explicitly");
+ throw new NpgsqlException("No username could be found, please specify one explicitly");
+ }
}
async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken, bool isFirstAttempt = true)
{
- var cert = default(X509Certificate2?);
try
{
if (async)
@@ -768,23 +818,23 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
#if NET5_0_OR_GREATER
// It's PEM time
var keyPath = Settings.SslKey ?? PostgresEnvironment.SslKey ?? PostgresEnvironment.SslKeyDefault;
- cert = string.IsNullOrEmpty(password)
+ _certificate = string.IsNullOrEmpty(password)
? X509Certificate2.CreateFromPemFile(certPath, keyPath)
: X509Certificate2.CreateFromEncryptedPemFile(certPath, password, keyPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows crypto API has a bug with pem certs
// See #3650
- using var previousCert = cert;
- cert = new X509Certificate2(cert.Export(X509ContentType.Pkcs12));
+ using var previousCert = _certificate;
+ _certificate = new X509Certificate2(_certificate.Export(X509ContentType.Pkcs12));
}
#else
throw new NotSupportedException("PEM certificates are only supported with .NET 5 and higher");
#endif
}
- cert ??= new X509Certificate2(certPath, password);
- clientCertificates.Add(cert);
+ _certificate ??= new X509Certificate2(certPath, password);
+ clientCertificates.Add(_certificate);
}
ClientCertificatesCallback?.Invoke(clientCertificates);
@@ -799,7 +849,7 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
throw new ArgumentException(string.Format(NpgsqlStrings.CannotUseSslVerifyWithUserCallback, sslMode));
if (Settings.RootCertificate is not null)
- throw new ArgumentException(string.Format(NpgsqlStrings.CannotUseSslRootCertificateWithUserCallback));
+ throw new ArgumentException(NpgsqlStrings.CannotUseSslRootCertificateWithUserCallback);
certificateValidationCallback = UserCertificateValidationCallback;
}
@@ -826,6 +876,18 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
certificateValidationCallback = SslVerifyFullValidation;
}
+ var host = Host;
+
+#if !NET8_0_OR_GREATER
+ // If the host is a valid IP address - replace it with an empty string
+ // We do that because .NET uses targetHost argument to send SNI to the server
+ // RFC explicitly prohibits sending an IP address so some servers might fail
+ // This was already fixed for .NET 8
+ // See #5543 for discussion
+ if (IPAddress.TryParse(host, out _))
+ host = string.Empty;
+#endif
+
timeout.CheckAndApply(this);
try
@@ -833,15 +895,18 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
var sslStream = new SslStream(_stream, leaveInnerStreamOpen: false, certificateValidationCallback);
var sslProtocols = SslProtocols.None;
- // On .NET Framework SslProtocols.None can be disabled, see #3718
#if NETSTANDARD2_0
- sslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
+ // On .NET Framework SslProtocols.None can be disabled, see #3718
+ if (DisableSystemDefaultTlsVersions)
+ {
+ sslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
+ }
#endif
if (async)
- await sslStream.AuthenticateAsClientAsync(Host, clientCertificates, sslProtocols, checkCertificateRevocation);
+ await sslStream.AuthenticateAsClientAsync(host, clientCertificates, sslProtocols, checkCertificateRevocation);
else
- sslStream.AuthenticateAsClient(Host, clientCertificates, sslProtocols, checkCertificateRevocation);
+ sslStream.AuthenticateAsClient(host, clientCertificates, sslProtocols, checkCertificateRevocation);
_stream = sslStream;
}
@@ -865,7 +930,8 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat
}
catch
{
- cert?.Dispose();
+ _certificate?.Dispose();
+ _certificate = null;
_stream?.Dispose();
_stream = null!;
@@ -965,17 +1031,16 @@ Task GetHostAddressesAsync(CancellationToken ct) =>
: (await TaskTimeoutAndCancellation.ExecuteAsync(GetHostAddressesAsync, timeout, cancellationToken))
.Select(a => new IPEndPoint(a, Port)).ToArray();
- // Give each IP an equal share of the remaining time
- var perIpTimespan = default(TimeSpan);
- var perIpTimeout = timeout;
+ // Give each endpoint an equal share of the remaining time
+ var perEndpointTimeout = default(TimeSpan);
if (timeout.IsSet)
- {
- perIpTimespan = new TimeSpan(timeout.CheckAndGetTimeLeft().Ticks / endpoints.Length);
- perIpTimeout = new NpgsqlTimeout(perIpTimespan);
- }
+ perEndpointTimeout = TimeSpan.FromTicks(timeout.CheckAndGetTimeLeft().Ticks / endpoints.Length);
for (var i = 0; i < endpoints.Length; i++)
{
+ var endpointTimeout = timeout.IsSet ? new NpgsqlTimeout(perEndpointTimeout) : timeout;
+ Debug.Assert(timeout.IsSet == endpointTimeout.IsSet);
+
var endpoint = endpoints[i];
ConnectionLogger.LogTrace("Attempting to connect to {Endpoint}", endpoint);
var protocolType =
@@ -986,7 +1051,7 @@ Task GetHostAddressesAsync(CancellationToken ct) =>
var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, protocolType);
try
{
- await OpenSocketConnectionAsync(socket, endpoint, perIpTimeout, cancellationToken);
+ await OpenSocketConnectionAsync(socket, endpoint, endpointTimeout, cancellationToken);
SetSocketOptions(socket);
_socket = socket;
ConnectedEndPoint = endpoint;
@@ -1264,9 +1329,20 @@ internal ValueTask ReadMessage(bool async, DataRowLoadingMode d
connector.ReadBuffer.Timeout = TimeSpan.FromMilliseconds(connector.InternalCommandTimeout);
for (; connector.PendingPrependedResponses > 0; connector.PendingPrependedResponses--)
await ReadMessageLong(connector, async, DataRowLoadingMode.Skip, readingNotifications: false, isReadingPrependedMessage: true);
+ // We've read all the prepended response.
+ // Allow cancellation to proceed.
+ connector.ReadingPrependedMessagesMRE.Set();
+
+ // User requested cancellation but it hasn't been performed yet.
+ // This might happen if the cancellation is requested while we're reading prepended responses
+ // because we shouldn't cancel them and otherwise might deadlock.
+ if (connector.UserCancellationRequested && !connector.PostgresCancellationPerformed)
+ connector.PerformDelayedUserCancellation();
}
- catch (PostgresException e)
+ catch (Exception e)
{
+ // Prepended queries should never fail.
+ // If they do, we're not even going to attempt to salvage the connector.
throw connector.Break(e);
}
}
@@ -1633,52 +1709,74 @@ static RemoteCertificateValidationCallback SslRootValidation(string certRootPath
#region Cancel
- internal void PerformUserCancellation()
+ internal void ResetCancellation()
+ {
+ // If a cancellation is in progress, wait for it to "complete" before proceeding (#615)
+ lock (CancelLock)
+ {
+ if (PendingPrependedResponses > 0)
+ ReadingPrependedMessagesMRE.Reset();
+ Debug.Assert(ReadingPrependedMessagesMRE.IsSet || PendingPrependedResponses > 0);
+ }
+ }
+
+ internal void PerformImmediateUserCancellation()
{
var connection = Connection;
- if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader)
+ if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader || UserCancellationRequested)
return;
- // There's a subtle race condition where cancellation may be happening just as Break is called. Break takes the connector lock, and
- // then ends the user action; this disposes the cancellation token registration, which waits until the cancellation callback
- // completes. But the callback needs to take the connector lock below, which led to a deadlock (#4654).
- // As a result, Break takes CancelLock, and we abort the cancellation attempt immediately if we can't get it here.
- if (!Monitor.TryEnter(CancelLock))
- return;
+ // Take the lock first to make sure there is no concurrent Break.
+ // We should be safe to take it as Break only take it to set the state.
+ lock (SyncObj)
+ {
+ // The connector is dead, exit gracefully.
+ if (!IsConnected)
+ return;
+ // The connector is still alive, take the CancelLock before exiting SingleUseLock.
+ // If a break will happen after, it's going to wait for the cancellation to complete.
+ Monitor.Enter(CancelLock);
+ }
try
{
+ // Set the flag first before waiting on ReadingPrependedMessagesMRE.
+ // That way we're making sure that in case we're racing with ReadingPrependedMessagesMRE.Set
+ // that it's going to read the new value of the flag and request cancellation
_userCancellationRequested = true;
- if (AttemptPostgresCancellation && SupportsPostgresCancellation)
- {
- var cancellationTimeout = Settings.CancellationTimeout;
- if (PerformPostgresCancellation() && cancellationTimeout >= 0)
- {
- if (cancellationTimeout > 0)
- {
- lock (this)
- {
- if (!IsConnected)
- return;
- UserTimeout = cancellationTimeout;
- ReadBuffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout);
- ReadBuffer.Cts.CancelAfter(cancellationTimeout);
- }
- }
+ // Check whether we've read all responses for the prepended queries
+ // as we can't gracefully handle their cancellation.
+ // We don't wait indefinitely to avoid deadlocks from synchronous CancellationToken.Register
+ // See #5032
+ if (!ReadingPrependedMessagesMRE.Wait(0))
+ return;
- return;
- }
- }
+ PerformUserCancellationUnsynchronized();
+ }
+ finally
+ {
+ Monitor.Exit(CancelLock);
+ }
+ }
- lock (this)
- {
- if (!IsConnected)
- return;
- UserTimeout = -1;
- ReadBuffer.Timeout = _cancelImmediatelyTimeout;
- ReadBuffer.Cts.Cancel();
- }
+ void PerformDelayedUserCancellation()
+ {
+ // Take the lock first to make sure there is no concurrent Break.
+ // We should be safe to take it as Break only take it to set the state.
+ lock (SyncObj)
+ {
+ // The connector is dead, exit gracefully.
+ if (!IsConnected)
+ return;
+ // The connector is still alive, take the CancelLock before exiting SingleUseLock.
+ // If a break will happen after, it's going to wait for the cancellation to complete.
+ Monitor.Enter(CancelLock);
+ }
+
+ try
+ {
+ PerformUserCancellationUnsynchronized();
}
finally
{
@@ -1686,6 +1784,29 @@ internal void PerformUserCancellation()
}
}
+ void PerformUserCancellationUnsynchronized()
+ {
+ if (AttemptPostgresCancellation && SupportsPostgresCancellation)
+ {
+ var cancellationTimeout = Settings.CancellationTimeout;
+ if (PerformPostgresCancellation() && cancellationTimeout >= 0)
+ {
+ if (cancellationTimeout > 0)
+ {
+ UserTimeout = cancellationTimeout;
+ ReadBuffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout);
+ ReadBuffer.Cts.CancelAfter(cancellationTimeout);
+ }
+
+ return;
+ }
+ }
+
+ UserTimeout = -1;
+ ReadBuffer.Timeout = _cancelImmediatelyTimeout;
+ ReadBuffer.Cts.Cancel();
+ }
+
///
/// Creates another connector and sends a cancel request through it for this connector. This method never throws, but returns
/// whether the cancellation attempt failed.
@@ -1753,8 +1874,7 @@ void DoCancelRequest(int backendProcessId, int backendSecretKey)
}
finally
{
- lock (this)
- FullCleanup();
+ FullCleanup();
}
}
@@ -1769,7 +1889,7 @@ internal CancellationTokenRegistration StartCancellableOperation(
AttemptPostgresCancellation = attemptPgCancellation;
return _cancellationTokenRegistration =
- cancellationToken.Register(static c => ((NpgsqlConnector)c!).PerformUserCancellation(), this);
+ cancellationToken.Register(static c => ((NpgsqlConnector)c!).PerformImmediateUserCancellation(), this);
}
///
@@ -1800,7 +1920,7 @@ internal CancellationTokenRegistration StartNestedCancellableOperation(
AttemptPostgresCancellation = attemptPgCancellation;
return _cancellationTokenRegistration =
- cancellationToken.Register(static c => ((NpgsqlConnector)c!).PerformUserCancellation(), this);
+ cancellationToken.Register(static c => ((NpgsqlConnector)c!).PerformImmediateUserCancellation(), this);
}
#endregion Cancel
@@ -1860,7 +1980,7 @@ copyOperation is NpgsqlCopyTextWriter ||
// very unlikely to block (plus locking would need to be worked out)
internal void Close()
{
- lock (this)
+ lock (SyncObj)
{
if (IsReady)
{
@@ -1889,13 +2009,11 @@ internal void Close()
}
State = ConnectorState.Closed;
- FullCleanup();
- LogMessages.ClosedPhysicalConnection(ConnectionLogger, Host, Port, Database, UserFacingConnectionString, Id);
}
- }
- internal bool TryRemovePendingEnlistedConnector(Transaction transaction)
- => DataSource.TryRemovePendingEnlistedConnector(this, transaction);
+ FullCleanup();
+ LogMessages.ClosedPhysicalConnection(ConnectionLogger, Host, Port, Database, UserFacingConnectionString, Id);
+ }
internal void Return() => DataSource.Return(this);
@@ -1920,86 +2038,114 @@ internal Exception Break(Exception reason)
{
Debug.Assert(!IsClosed);
- // See PerformUserCancellation on why we take CancelLock
- lock (CancelLock)
- lock (this)
- {
- if (State == ConnectorState.Broken)
- return reason;
+ Monitor.Enter(SyncObj);
- // Note we only set the cluster to offline and clear the pool if the connection is being broken (we're in this method),
- // *and* the exception indicates that the PG cluster really is down; the latter includes any IO/timeout issue,
- // but does not include e.g. authentication failure or timeouts with disabled cancellation.
- if (reason is NpgsqlException { IsTransient: true } ne &&
- (ne.InnerException is not TimeoutException || Settings.CancellationTimeout != -1) ||
- reason is PostgresException pe && PostgresErrorCodes.IsCriticalFailure(pe))
- {
- DataSource.UpdateDatabaseState(DatabaseState.Offline, DateTime.UtcNow, Settings.HostRecheckSecondsTranslated);
- DataSource.Clear();
- }
+ if (State == ConnectorState.Broken)
+ {
+ // We're already broken.
+ // Exit SingleUseLock to unblock other threads (like cancellation).
+ Monitor.Exit(SyncObj);
+ // Wait for the break to complete before going forward.
+ lock (CleanupLock) { }
+ return reason;
+ }
+ try
+ {
LogMessages.BreakingConnection(ConnectionLogger, Id, reason);
// Note that we may be reading and writing from the same connector concurrently, so safely set
// the original reason for the break before actually closing the socket etc.
Interlocked.CompareExchange(ref _breakReason, reason, null);
State = ConnectorState.Broken;
+ // Take the CleanupLock while in SingleUseLock to make sure concurrent Break doesn't take it first.
+ Monitor.Enter(CleanupLock);
+ }
+ finally
+ {
+ // Unblock other threads (like cancellation) to proceed and exit gracefully.
+ Monitor.Exit(SyncObj);
+ }
+
+ try
+ {
+ // Make sure there is no concurrent cancellation in process
+ lock (CancelLock)
+ {
+ // Note we only set the cluster to offline and clear the pool if the connection is being broken (we're in this method),
+ // *and* the exception indicates that the PG cluster really is down; the latter includes any IO/timeout issue,
+ // but does not include e.g. authentication failure or timeouts with disabled cancellation.
+ if (reason is NpgsqlException { IsTransient: true } ne &&
+ (ne.InnerException is not TimeoutException || Settings.CancellationTimeout != -1) ||
+ reason is PostgresException pe && PostgresErrorCodes.IsCriticalFailure(pe))
+ {
+ DataSource.UpdateDatabaseState(DatabaseState.Offline, DateTime.UtcNow, Settings.HostRecheckSecondsTranslated);
+ DataSource.Clear();
+ }
- var connection = Connection;
+ var connection = Connection;
- FullCleanup();
+ FullCleanup();
- if (connection is not null)
- {
- var closeLockTaken = connection.TakeCloseLock();
- Debug.Assert(closeLockTaken);
- if (Settings.ReplicationMode == ReplicationMode.Off)
+ if (connection is not null)
{
- // When a connector is broken, we immediately "return" it to the pool (i.e. update the pool state so reflect the
- // connector no longer being open). Upper layers such as EF may check DbConnection.ConnectionState, and only close if
- // it's closed; so we can't set the state to Closed and expect the user to still close (in order to return to the pool).
- // On the other hand leaving the state Open could indicate to the user that the connection is functional.
- // (see https://github.com/npgsql/npgsql/issues/3705#issuecomment-839908772)
- Connection = null;
- if (connection.ConnectorBindingScope != ConnectorBindingScope.None)
- Return();
- connection.EnlistedTransaction = null;
- connection.Connector = null;
- connection.ConnectorBindingScope = ConnectorBindingScope.None;
+ var closeLockTaken = connection.TakeCloseLock();
+ Debug.Assert(closeLockTaken);
+ if (Settings.ReplicationMode == ReplicationMode.Off)
+ {
+ // When a connector is broken, we immediately "return" it to the pool (i.e. update the pool state so reflect the
+ // connector no longer being open). Upper layers such as EF may check DbConnection.ConnectionState, and only close if
+ // it's closed; so we can't set the state to Closed and expect the user to still close (in order to return to the pool).
+ // On the other hand leaving the state Open could indicate to the user that the connection is functional.
+ // (see https://github.com/npgsql/npgsql/issues/3705#issuecomment-839908772)
+ Connection = null;
+ if (connection.ConnectorBindingScope != ConnectorBindingScope.None)
+ Return();
+ connection.EnlistedTransaction = null;
+ connection.Connector = null;
+ connection.ConnectorBindingScope = ConnectorBindingScope.None;
+ }
+
+ connection.FullState = ConnectionState.Broken;
+ connection.ReleaseCloseLock();
}
- connection.FullState = ConnectionState.Broken;
- connection.ReleaseCloseLock();
+ return reason;
}
-
- return reason;
+ }
+ finally
+ {
+ Monitor.Exit(CleanupLock);
}
}
-
+
void FullCleanup()
{
- Debug.Assert(Monitor.IsEntered(this));
-
- if (Settings.Multiplexing)
+ lock (CleanupLock)
{
- FlagAsNotWritableForMultiplexing();
+ if (Settings.Multiplexing)
+ {
+ FlagAsNotWritableForMultiplexing();
- // Note that in multiplexing, this could be called from the read loop, while the write loop is
- // writing into the channel. To make sure this race condition isn't a problem, the channel currently
- // isn't set up with SingleWriter (since at this point it doesn't do anything).
- CommandsInFlightWriter!.Complete();
+ // Note that in multiplexing, this could be called from the read loop, while the write loop is
+ // writing into the channel. To make sure this race condition isn't a problem, the channel currently
+ // isn't set up with SingleWriter (since at this point it doesn't do anything).
+ CommandsInFlightWriter!.Complete();
- // The connector's read loop has a continuation to observe and log any exception coming out
- // (see Open)
- }
+ // The connector's read loop has a continuation to observe and log any exception coming out
+ // (see Open)
+ }
- ConnectionLogger.LogTrace("Cleaning up connector", Id);
- Cleanup();
+ ConnectionLogger.LogTrace("Cleaning up connector", Id);
+ Cleanup();
- if (_isKeepAliveEnabled)
- {
- _keepAliveTimer!.Change(Timeout.Infinite, Timeout.Infinite);
- _keepAliveTimer.Dispose();
+ if (_isKeepAliveEnabled)
+ {
+ _keepAliveTimer!.Change(Timeout.Infinite, Timeout.Infinite);
+ _keepAliveTimer.Dispose();
+ }
+
+ ReadingPrependedMessagesMRE.Dispose();
}
}
@@ -2064,6 +2210,12 @@ void Cleanup()
Connection = null;
PostgresParameters.Clear();
_currentCommand = null;
+
+ if (_certificate is not null)
+ {
+ _certificate.Dispose();
+ _certificate = null;
+ }
}
void GenerateResetMessage()
@@ -2183,6 +2335,20 @@ void ResetReadBuffer()
{
if (_origReadBuffer != null)
{
+ Debug.Assert(_origReadBuffer.ReadBytesLeft == 0);
+ Debug.Assert(_origReadBuffer.ReadPosition == 0);
+ if (ReadBuffer.ReadBytesLeft > 0)
+ {
+ // There is still something in the buffer which we haven't read yet
+ // In most cases it's ParameterStatus which can be sent asynchronously
+ // If in some extreme case we have too much data left in the buffer to store in the original buffer
+ // we just leave the oversize buffer as is and will try again on next reset
+ if (ReadBuffer.ReadBytesLeft > _origReadBuffer.Size)
+ return;
+
+ ReadBuffer.CopyTo(_origReadBuffer);
+ }
+
ReadBuffer.Dispose();
ReadBuffer = _origReadBuffer;
_origReadBuffer = null;
@@ -2243,7 +2409,7 @@ internal UserAction StartUserAction(
if (!_isKeepAliveEnabled)
return DoStartUserAction(newState, command);
- lock (this)
+ lock (SyncObj)
{
if (!IsConnected)
{
@@ -2320,7 +2486,7 @@ internal void EndUserAction()
if (_isKeepAliveEnabled)
{
- lock (this)
+ lock (SyncObj)
{
if (IsReady || !IsConnected)
return;
@@ -2362,10 +2528,7 @@ internal void EndUserAction()
void PerformKeepAlive(object? state)
{
Debug.Assert(_isKeepAliveEnabled);
-
- // SemaphoreSlim.Dispose() isn't thread-safe - it may be in progress so we shouldn't try to wait on it;
- // we need a standard lock to protect it.
- if (!Monitor.TryEnter(this))
+ if (!Monitor.TryEnter(SyncObj))
return;
try
@@ -2398,7 +2561,7 @@ void PerformKeepAlive(object? state)
}
finally
{
- Monitor.Exit(this);
+ Monitor.Exit(SyncObj);
}
}
#pragma warning restore CA1801 // Review unused parameters
@@ -2536,6 +2699,12 @@ internal async Task ExecuteInternalCommand(byte[] data, bool async, Cancellation
/// A object.
public NpgsqlCommand CreateCommand(string? cmdText = null) => new(cmdText, this);
+ ///
+ /// Creates and returns a object associated with the .
+ ///
+ /// A object.
+ public NpgsqlBatch CreateBatch() => new NpgsqlBatch(this);
+
void ReadParameterStatus(ReadOnlySpan incomingName, ReadOnlySpan incomingValue)
{
byte[] rawName;
diff --git a/src/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs b/src/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs
index 39ebad22a7..a9e6ebc134 100644
--- a/src/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs
+++ b/src/Npgsql/Internal/NpgsqlReadBuffer.Stream.cs
@@ -65,7 +65,7 @@ public override long Position
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value), "Non - negative number required.");
- Seek(_start + value, SeekOrigin.Begin);
+ Seek(value, SeekOrigin.Begin);
}
}
@@ -87,8 +87,9 @@ public override long Seek(long offset, SeekOrigin origin)
var tempPosition = unchecked(_start + (int)offset);
if (offset < 0 || tempPosition < _start)
throw new IOException(seekBeforeBegin);
- _buf.ReadPosition = _start;
- return tempPosition;
+ _buf.ReadPosition = tempPosition;
+ _read = (int)offset;
+ return _read;
}
case SeekOrigin.Current:
{
@@ -96,15 +97,17 @@ public override long Seek(long offset, SeekOrigin origin)
if (unchecked(_buf.ReadPosition + offset) < _start || tempPosition < _start)
throw new IOException(seekBeforeBegin);
_buf.ReadPosition = tempPosition;
- return tempPosition;
+ _read += (int)offset;
+ return _read;
}
case SeekOrigin.End:
{
- var tempPosition = unchecked(_len + (int)offset);
- if (unchecked(_len + offset) < _start || tempPosition < _start)
+ var tempPosition = unchecked(_start + _len + (int)offset);
+ if (unchecked(_start + _len + offset) < _start || tempPosition < _start)
throw new IOException(seekBeforeBegin);
_buf.ReadPosition = tempPosition;
- return tempPosition;
+ _read = _len + (int)offset;
+ return _read;
}
default:
throw new ArgumentOutOfRangeException(nameof(origin), "Invalid seek origin.");
@@ -238,4 +241,4 @@ static void ValidateArguments(byte[] buffer, int offset, int count)
if (buffer.Length - offset < count)
throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.");
}
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/Internal/NpgsqlReadBuffer.cs b/src/Npgsql/Internal/NpgsqlReadBuffer.cs
index 50ef859a76..83718fae16 100644
--- a/src/Npgsql/Internal/NpgsqlReadBuffer.cs
+++ b/src/Npgsql/Internal/NpgsqlReadBuffer.cs
@@ -220,35 +220,32 @@ static async Task EnsureLong(
// See #4305.
isStreamBroken = connector.IsSecure && e is IOException;
#endif
-
- if (!isStreamBroken)
+ // When reading notifications (Wait), just throw TimeoutException or
+ // OperationCanceledException immediately.
+ // Nothing to cancel, and no breaking of the connection.
+ if (readingNotifications && !isStreamBroken)
+ throw CreateException(connector);
+
+ // If we should attempt PostgreSQL cancellation, do it the first time we get a timeout.
+ // TODO: As an optimization, we can still attempt to send a cancellation request, but after
+ // that immediately break the connection
+ if (connector.AttemptPostgresCancellation &&
+ !connector.PostgresCancellationPerformed &&
+ connector.PerformPostgresCancellation() &&
+ !isStreamBroken)
{
- // When reading notifications (Wait), just throw TimeoutException or
- // OperationCanceledException immediately.
- // Nothing to cancel, and no breaking of the connection.
- if (readingNotifications)
- throw CreateException(connector);
-
- // If we should attempt PostgreSQL cancellation, do it the first time we get a timeout.
- // TODO: As an optimization, we can still attempt to send a cancellation request, but after
- // that immediately break the connection
- if (connector.AttemptPostgresCancellation &&
- !connector.PostgresCancellationPerformed &&
- connector.PerformPostgresCancellation())
+ // Note that if the cancellation timeout is negative, we flow down and break the
+ // connection immediately.
+ var cancellationTimeout = connector.Settings.CancellationTimeout;
+ if (cancellationTimeout >= 0)
{
- // Note that if the cancellation timeout is negative, we flow down and break the
- // connection immediately.
- var cancellationTimeout = connector.Settings.CancellationTimeout;
- if (cancellationTimeout >= 0)
- {
- if (cancellationTimeout > 0)
- buffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout);
-
- if (async)
- finalCt = buffer.Cts.Start();
-
- continue;
- }
+ if (cancellationTimeout > 0)
+ buffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout);
+
+ if (async)
+ finalCt = buffer.Cts.Start();
+
+ continue;
}
}
@@ -563,7 +560,7 @@ public TextReader GetPreparedTextReader(string str, Stream stream)
{
if (_preparedTextReader is not { IsDisposed: true })
_preparedTextReader = new PreparedTextReader();
-
+
_preparedTextReader.Init(str, (ColumnStream)stream);
return _preparedTextReader;
}
diff --git a/src/Npgsql/Internal/NpgsqlWriteBuffer.cs b/src/Npgsql/Internal/NpgsqlWriteBuffer.cs
index 1a89cff985..6080feefcf 100644
--- a/src/Npgsql/Internal/NpgsqlWriteBuffer.cs
+++ b/src/Npgsql/Internal/NpgsqlWriteBuffer.cs
@@ -28,6 +28,7 @@ public sealed partial class NpgsqlWriteBuffer : IDisposable
internal Stream Underlying { private get; set; }
readonly Socket? _underlyingSocket;
+ internal bool MessageLengthValidation { get; set; } = true;
readonly ResettableCancellationTokenSource _timeoutCts;
@@ -72,6 +73,9 @@ internal TimeSpan Timeout
internal int WritePosition;
+ int _messageBytesFlushed;
+ int? _messageLength;
+
ParameterStream? _parameterStream;
bool _disposed;
@@ -126,6 +130,8 @@ public async Task Flush(bool async, CancellationToken cancellationToken = defaul
WritePosition = pos;
} else if (WritePosition == 0)
return;
+ else
+ AdvanceMessageBytesFlushed(WritePosition);
var finalCt = async && Timeout > TimeSpan.Zero
? _timeoutCts.Start(cancellationToken)
@@ -137,7 +143,7 @@ public async Task Flush(bool async, CancellationToken cancellationToken = defaul
{
await Underlying.WriteAsync(Buffer, 0, WritePosition, finalCt);
await Underlying.FlushAsync(finalCt);
- if (Timeout > TimeSpan.Zero)
+ if (Timeout > TimeSpan.Zero)
_timeoutCts.Stop();
}
else
@@ -194,15 +200,19 @@ internal void DirectWrite(ReadOnlySpan buffer)
Debug.Assert(WritePosition == 5);
WritePosition = 1;
- WriteInt32(buffer.Length + 4);
+ WriteInt32(checked(buffer.Length + 4));
WritePosition = 5;
_copyMode = false;
+ StartMessage(5);
Flush();
_copyMode = true;
WriteCopyDataHeader(); // And ready the buffer after the direct write completes
}
else
+ {
Debug.Assert(WritePosition == 0);
+ AdvanceMessageBytesFlushed(buffer.Length);
+ }
try
{
@@ -225,15 +235,19 @@ internal async Task DirectWrite(ReadOnlyMemory memory, bool async, Cancell
Debug.Assert(WritePosition == 5);
WritePosition = 1;
- WriteInt32(memory.Length + 4);
+ WriteInt32(checked(memory.Length + 4));
WritePosition = 5;
_copyMode = false;
+ StartMessage(5);
await Flush(async, cancellationToken);
_copyMode = true;
WriteCopyDataHeader(); // And ready the buffer after the direct write completes
}
else
+ {
Debug.Assert(WritePosition == 0);
+ AdvanceMessageBytesFlushed(memory.Length);
+ }
try
{
@@ -606,9 +620,51 @@ public void Dispose()
#region Misc
+ internal void StartMessage(int messageLength)
+ {
+ if (!MessageLengthValidation)
+ return;
+
+ if (_messageLength is not null && _messageBytesFlushed != _messageLength && WritePosition != -_messageBytesFlushed + _messageLength)
+ Throw();
+
+ // Add negative WritePosition to compensate for previous message(s) written without flushing.
+ _messageBytesFlushed = -WritePosition;
+ _messageLength = messageLength;
+
+ void Throw()
+ {
+ throw Connector.Break(new OverflowException("Did not write the amount of bytes the message length specified"));
+ }
+ }
+
+ void AdvanceMessageBytesFlushed(int count)
+ {
+ if (!MessageLengthValidation)
+ return;
+
+ if (count < 0 || _messageLength is null || (long)_messageBytesFlushed + count > _messageLength)
+ Throw();
+
+ _messageBytesFlushed += count;
+
+ void Throw()
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count), "Can't advance by a negative count");
+
+ if (_messageLength is null)
+ throw Connector.Break(new InvalidOperationException("No message was started"));
+
+ if ((long)_messageBytesFlushed + count > _messageLength)
+ throw Connector.Break(new OverflowException("Tried to write more bytes than the message length specified"));
+ }
+ }
+
internal void Clear()
{
WritePosition = 0;
+ _messageLength = null;
}
///
@@ -623,4 +679,4 @@ internal byte[] GetContents()
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs b/src/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs
index 42bcb93d42..0831306a67 100644
--- a/src/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs
+++ b/src/Npgsql/Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs
@@ -76,7 +76,7 @@ public override void Write(DateTime value, NpgsqlWriteBuffer buf, NpgsqlParamete
}
}
- buf.WriteInt32((value - BaseValueDateTime).Days);
+ buf.WriteInt32((value.Date - BaseValueDateTime).Days);
}
///
diff --git a/src/Npgsql/Internal/TypeHandlers/TextHandler.cs b/src/Npgsql/Internal/TypeHandlers/TextHandler.cs
index e3c5f957d4..965bd9c348 100644
--- a/src/Npgsql/Internal/TypeHandlers/TextHandler.cs
+++ b/src/Npgsql/Internal/TypeHandlers/TextHandler.cs
@@ -110,26 +110,32 @@ async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, in
async ValueTask INpgsqlTypeHandler.Read(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription)
{
// Make sure we have enough bytes in the buffer for a single character
+ // We can get here a much bigger length in case it's a string
+ // while we want to read only its first character
var maxBytes = Math.Min(buf.TextEncoding.GetMaxByteCount(1), len);
while (buf.ReadBytesLeft < maxBytes)
await buf.ReadMore(async);
- return ReadCharCore();
+ var character = ReadCharCore();
- unsafe char ReadCharCore()
+ // We've been requested to read 'len' bytes, which is why we're going to skip them
+ // This is important for NpgsqlDataReader with CommandBehavior.SequentialAccess
+ // which tracks how many bytes it has to skip for the next column
+ await buf.Skip(len, async);
+ return character;
+
+ char ReadCharCore()
{
var decoder = buf.TextEncoding.GetDecoder();
#if NETSTANDARD2_0
var singleCharArray = new char[1];
- decoder.Convert(buf.Buffer, buf.ReadPosition, maxBytes, singleCharArray, 0, 1, true, out var bytesUsed, out var charsUsed, out _);
+ decoder.Convert(buf.Buffer, buf.ReadPosition, maxBytes, singleCharArray, 0, 1, true, out _, out var charsUsed, out _);
#else
Span singleCharArray = stackalloc char[1];
- decoder.Convert(buf.Buffer.AsSpan(buf.ReadPosition, maxBytes), singleCharArray, true, out var bytesUsed, out var charsUsed, out _);
+ decoder.Convert(buf.Buffer.AsSpan(buf.ReadPosition, maxBytes), singleCharArray, true, out _, out var charsUsed, out _);
#endif
- buf.Skip(len - bytesUsed);
-
if (charsUsed < 1)
throw new NpgsqlException("Could not read char - string was empty");
@@ -300,8 +306,8 @@ public Task Write(byte[] value, NpgsqlWriteBuffer buf, NpgsqlLengthCache? length
public virtual TextReader GetTextReader(Stream stream, NpgsqlReadBuffer buffer)
{
var byteLength = (int)(stream.Length - stream.Position);
- return buffer.ReadBytesLeft >= byteLength
- ? buffer.GetPreparedTextReader(_encoding.GetString(buffer.Buffer, buffer.ReadPosition, byteLength), stream)
+ return buffer.ReadBytesLeft >= byteLength
+ ? buffer.GetPreparedTextReader(_encoding.GetString(buffer.Buffer, buffer.ReadPosition, byteLength), stream)
: new StreamReader(stream, _encoding);
}
}
diff --git a/src/Npgsql/KerberosUsernameProvider.cs b/src/Npgsql/KerberosUsernameProvider.cs
index e2342775dd..63cc42fb88 100644
--- a/src/Npgsql/KerberosUsernameProvider.cs
+++ b/src/Npgsql/KerberosUsernameProvider.cs
@@ -18,19 +18,16 @@ sealed class KerberosUsernameProvider
static string? _principalWithRealm;
static string? _principalWithoutRealm;
-#pragma warning disable CS1998
- internal static async ValueTask GetUsernameAsync(bool includeRealm, ILogger connectionLogger, bool async, CancellationToken cancellationToken)
-#pragma warning restore CS1998
+ internal static ValueTask GetUsernameAsync(bool includeRealm, ILogger connectionLogger, bool async, CancellationToken cancellationToken)
{
if (_performedDetection)
- return includeRealm ? _principalWithRealm : _principalWithoutRealm;
+ return new(includeRealm ? _principalWithRealm : _principalWithoutRealm);
var klistPath = FindInPath("klist");
if (klistPath == null)
{
connectionLogger.LogDebug("klist not found in PATH, skipping Kerberos username detection");
- return null;
+ return new((string?)null);
}
-
var processStartInfo = new ProcessStartInfo
{
FileName = klistPath,
@@ -38,46 +35,54 @@ sealed class KerberosUsernameProvider
RedirectStandardError = true,
UseShellExecute = false
};
+
var process = Process.Start(processStartInfo);
if (process is null)
{
connectionLogger.LogDebug("klist process could not be started");
- return null;
+ return new((string?)null);
}
+ return GetUsernameAsyncInternal();
+
+#pragma warning disable CS1998
+ async ValueTask GetUsernameAsyncInternal()
+#pragma warning restore CS1998
+ {
#if NET5_0_OR_GREATER
- if (async)
- await process.WaitForExitAsync(cancellationToken);
- else
- // ReSharper disable once MethodHasAsyncOverloadWithCancellation
- process.WaitForExit();
+ if (async)
+ await process.WaitForExitAsync(cancellationToken);
+ else
+ // ReSharper disable once MethodHasAsyncOverloadWithCancellation
+ process.WaitForExit();
#else
// ReSharper disable once MethodHasAsyncOverload
process.WaitForExit();
#endif
- if (process.ExitCode != 0)
- {
- connectionLogger.LogDebug($"klist exited with code {process.ExitCode}: {process.StandardError.ReadToEnd()}");
- return null;
- }
+ if (process.ExitCode != 0)
+ {
+ connectionLogger.LogDebug($"klist exited with code {process.ExitCode}: {process.StandardError.ReadToEnd()}");
+ return null;
+ }
- var line = default(string);
- for (var i = 0; i < 2; i++)
- // ReSharper disable once MethodHasAsyncOverload
+ var line = default(string);
+ for (var i = 0; i < 2; i++)
+ // ReSharper disable once MethodHasAsyncOverload
#if NET7_0_OR_GREATER
- if ((line = async ? await process.StandardOutput.ReadLineAsync(cancellationToken) : process.StandardOutput.ReadLine()) == null)
+ if ((line = async ? await process.StandardOutput.ReadLineAsync(cancellationToken) : process.StandardOutput.ReadLine()) == null)
#elif NET5_0_OR_GREATER
- if ((line = async ? await process.StandardOutput.ReadLineAsync() : process.StandardOutput.ReadLine()) == null)
+ if ((line = async ? await process.StandardOutput.ReadLineAsync() : process.StandardOutput.ReadLine()) == null)
#else
- if ((line = process.StandardOutput.ReadLine()) == null)
+ if ((line = process.StandardOutput.ReadLine()) == null)
#endif
- {
- connectionLogger.LogDebug("Unexpected output from klist, aborting Kerberos username detection");
- return null;
- }
+ {
+ connectionLogger.LogDebug("Unexpected output from klist, aborting Kerberos username detection");
+ return null;
+ }
- return ParseKListOutput(line!, includeRealm, connectionLogger);
+ return ParseKListOutput(line!, includeRealm, connectionLogger);
+ }
}
static string? ParseKListOutput(string line, bool includeRealm, ILogger connectionLogger)
diff --git a/src/Npgsql/MultiplexingDataSource.cs b/src/Npgsql/MultiplexingDataSource.cs
index 2eb1763c3c..b64fae03fa 100644
--- a/src/Npgsql/MultiplexingDataSource.cs
+++ b/src/Npgsql/MultiplexingDataSource.cs
@@ -32,9 +32,8 @@ sealed class MultiplexingDataSource : PoolingDataSource
internal MultiplexingDataSource(
NpgsqlConnectionStringBuilder settings,
- NpgsqlDataSourceConfiguration dataSourceConfig,
- NpgsqlMultiHostDataSource? parentPool = null)
- : base(settings, dataSourceConfig, parentPool)
+ NpgsqlDataSourceConfiguration dataSourceConfig)
+ : base(settings, dataSourceConfig)
{
Debug.Assert(Settings.Multiplexing);
@@ -98,7 +97,7 @@ async Task MultiplexingWriteLoop()
}
connector = await OpenNewConnector(
- command.Connection!,
+ command.InternalConnection!,
new NpgsqlTimeout(TimeSpan.FromSeconds(Settings.Timeout)),
async: true,
CancellationToken.None);
@@ -173,7 +172,7 @@ async Task MultiplexingWriteLoop()
{
stats.Reset();
connector.FlagAsNotWritableForMultiplexing();
- command.TraceCommandStart(connector);
+ command.TraceCommandEnrich(connector);
// Read queued commands and write them to the connector's buffer, for as long as we're
// under our write threshold and timer delay.
diff --git a/src/Npgsql/Npgsql.csproj b/src/Npgsql/Npgsql.csproj
index 368e04ca57..7dd3999425 100644
--- a/src/Npgsql/Npgsql.csproj
+++ b/src/Npgsql/Npgsql.csproj
@@ -13,7 +13,6 @@
-
@@ -38,6 +37,10 @@
+
+
+
+
diff --git a/src/Npgsql/NpgsqlActivitySource.cs b/src/Npgsql/NpgsqlActivitySource.cs
index 002cf4a638..9db0883cc4 100644
--- a/src/Npgsql/NpgsqlActivitySource.cs
+++ b/src/Npgsql/NpgsqlActivitySource.cs
@@ -1,5 +1,6 @@
using Npgsql.Internal;
using System;
+using System.Data;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
@@ -20,18 +21,65 @@ static NpgsqlActivitySource()
internal static bool IsEnabled => Source.HasListeners();
- internal static Activity? CommandStart(NpgsqlConnector connector, string sql)
+ internal static Activity? CommandStart(NpgsqlConnectionStringBuilder settings, string commandText, CommandType commandType)
{
- var settings = connector.Settings;
- var activity = Source.StartActivity(settings.Database!, ActivityKind.Client);
+ var dbName = settings.Database ?? "UNKNOWN";
+ string? dbOperation = null;
+ string? dbSqlTable = null;
+ string activityName;
+ switch (commandType)
+ {
+ case CommandType.StoredProcedure:
+ dbOperation = NpgsqlCommand.EnableStoredProcedureCompatMode ? "SELECT" : "CALL";
+ // In this case our activity name follows the concept of the CommandType.TableDirect case
+ // (" .") but replaces db.sql.table with the procedure name
+ // which seems to match the spec's intent without being explicitly specified that way (it suggests
+ // using the procedure name but doesn't mention using db.operation or db.name in that case).
+ activityName = $"{dbOperation} {dbName}.{commandText}";
+ break;
+ case CommandType.TableDirect:
+ dbOperation = "SELECT";
+ // The OpenTelemetry spec actually asks to include the database name into db.sql.table
+ // but then again mixes the concept of database and schema.
+ // As I interpret it, it actually wants db.sql.table to include the schema name and not the
+ // database name if the concept of schemas exists in the database system.
+ // This also makes sense in the context of the activity name which otherwise would include the
+ // database name twice.
+ dbSqlTable = commandText;
+ activityName = $"{dbOperation} {dbName}.{dbSqlTable}";
+ break;
+ case CommandType.Text:
+ activityName = dbName;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(commandType), commandType, null);
+ }
+
+ var activity = Source.StartActivity(activityName, ActivityKind.Client);
if (activity is not { IsAllDataRequested: true })
return activity;
+ activity.SetTag("db.statement", commandText);
+
+ if (dbOperation != null)
+ activity.SetTag("db.operation", dbOperation);
+ if (dbSqlTable != null)
+ activity.SetTag("db.sql.table", dbSqlTable);
+
+ return activity;
+ }
+
+ internal static void Enrich(Activity activity, NpgsqlConnector connector)
+ {
+ if (!activity.IsAllDataRequested)
+ return;
+
activity.SetTag("db.system", "postgresql");
activity.SetTag("db.connection_string", connector.UserFacingConnectionString);
- activity.SetTag("db.user", settings.Username);
- activity.SetTag("db.name", settings.Database);
- activity.SetTag("db.statement", sql);
+ activity.SetTag("db.user", connector.InferredUserName);
+ // We trace the actual (maybe inferred) database name we're connected to, even if it
+ // wasn't specified in the connection string
+ activity.SetTag("db.name", connector.Settings.Database ?? connector.InferredUserName);
activity.SetTag("db.connection_id", connector.Id);
var endPoint = connector.ConnectedEndPoint;
@@ -43,23 +91,24 @@ static NpgsqlActivitySource()
activity.SetTag("net.peer.ip", ipEndPoint.Address.ToString());
if (ipEndPoint.Port != 5432)
activity.SetTag("net.peer.port", ipEndPoint.Port);
- activity.SetTag("net.peer.name", settings.Host);
+ activity.SetTag("net.peer.name", connector.Host);
break;
case UnixDomainSocketEndPoint:
activity.SetTag("net.transport", "unix");
- activity.SetTag("net.peer.name", settings.Host);
+ activity.SetTag("net.peer.name", connector.Host);
break;
default:
throw new ArgumentOutOfRangeException("Invalid endpoint type: " + endPoint.GetType());
}
-
- return activity;
}
internal static void ReceivedFirstResponse(Activity activity)
{
+ if (!activity.IsAllDataRequested)
+ return;
+
var activityEvent = new ActivityEvent("received-first-response");
activity.AddEvent(activityEvent);
}
@@ -85,4 +134,4 @@ internal static void SetException(Activity activity, Exception ex, bool escaped
activity.SetTag("otel.status_description", ex is PostgresException pgEx ? pgEx.SqlState : ex.Message);
activity.Dispose();
}
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/NpgsqlBatch.cs b/src/Npgsql/NpgsqlBatch.cs
index 0b86bb3164..6022c7004a 100644
--- a/src/Npgsql/NpgsqlBatch.cs
+++ b/src/Npgsql/NpgsqlBatch.cs
@@ -118,7 +118,10 @@ private protected NpgsqlBatch(NpgsqlDataSourceCommand command)
}
///
- protected override DbBatchCommand CreateDbBatchCommand()
+ protected override DbBatchCommand CreateDbBatchCommand() => CreateBatchCommand();
+
+ ///
+ public new NpgsqlBatchCommand CreateBatchCommand()
=> new NpgsqlBatchCommand();
///
@@ -171,4 +174,4 @@ public override Task PrepareAsync(CancellationToken cancellationToken = default)
///
public override void Cancel() => Command.Cancel();
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/NpgsqlBatchCommand.cs b/src/Npgsql/NpgsqlBatchCommand.cs
index 78aedc1f7e..f60c3b57b8 100644
--- a/src/Npgsql/NpgsqlBatchCommand.cs
+++ b/src/Npgsql/NpgsqlBatchCommand.cs
@@ -20,7 +20,13 @@ public sealed class NpgsqlBatchCommand : DbBatchCommand
public override string CommandText
{
get => _commandText;
- set => _commandText = value ?? string.Empty;
+ set
+ {
+ _commandText = value ?? string.Empty;
+
+ ResetPreparation();
+ // TODO: Technically should do this also if the parameter list (or type) changes
+ }
}
///
@@ -153,6 +159,8 @@ internal PreparedStatement? PreparedStatement
PreparedStatement? _preparedStatement;
+ internal NpgsqlConnector? ConnectorPreparedOn { get; set; }
+
internal bool IsPreparing;
///
@@ -248,6 +256,8 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg)
OID = msg.OID;
}
+ internal void ResetPreparation() => ConnectorPreparedOn = null;
+
///
/// Returns the .
///
diff --git a/src/Npgsql/NpgsqlBinaryExporter.cs b/src/Npgsql/NpgsqlBinaryExporter.cs
index 5415411062..76dfe8b395 100644
--- a/src/Npgsql/NpgsqlBinaryExporter.cs
+++ b/src/Npgsql/NpgsqlBinaryExporter.cs
@@ -382,7 +382,7 @@ void CheckDisposed()
///
/// Cancels an ongoing export.
///
- public void Cancel() => _connector.PerformUserCancellation();
+ public void Cancel() => _connector.PerformImmediateUserCancellation();
///
/// Async cancels an ongoing export.
diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs
index e3d07c3ae9..1be30f29a3 100644
--- a/src/Npgsql/NpgsqlCommand.cs
+++ b/src/Npgsql/NpgsqlCommand.cs
@@ -196,6 +196,26 @@ public override string CommandText
}
}
+ string GetBatchFullCommandText()
+ {
+ Debug.Assert(IsWrappedByBatch);
+ if (InternalBatchCommands.Count == 0)
+ return string.Empty;
+ if (InternalBatchCommands.Count == 1)
+ return InternalBatchCommands[0].CommandText;
+ // TODO: Potentially cache on connector/command?
+ var sb = new StringBuilder();
+ sb.Append(InternalBatchCommands[0].CommandText);
+ for (var i = 1; i < InternalBatchCommands.Count; i++)
+ {
+ sb
+ .Append(';')
+ .AppendLine()
+ .Append(InternalBatchCommands[i].CommandText);
+ }
+ return sb.ToString();
+ }
+
///
/// Gets or sets the wait time (in seconds) before terminating the attempt to execute a command and generating an error.
///
@@ -545,58 +565,72 @@ void DeriveParametersForQuery(NpgsqlConnector connector)
if (sendTask.IsFaulted)
sendTask.GetAwaiter().GetResult();
- foreach (var batchCommand in InternalBatchCommands)
+ try
{
- Expect(
- connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector);
- var paramTypeOIDs = Expect(
- connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector).TypeOIDs;
-
- if (batchCommand.PositionalParameters.Count != paramTypeOIDs.Count)
+ foreach (var batchCommand in InternalBatchCommands)
{
- connector.SkipUntil(BackendMessageCode.ReadyForQuery);
- Parameters.Clear();
- throw new NpgsqlException("There was a mismatch in the number of derived parameters between the Npgsql SQL parser and the PostgreSQL parser. Please report this as bug to the Npgsql developers (https://github.com/npgsql/npgsql/issues).");
- }
+ Expect(
+ connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector);
+ var paramTypeOIDs = Expect(
+ connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector).TypeOIDs;
- for (var i = 0; i < paramTypeOIDs.Count; i++)
- {
- try
+ if (batchCommand.PositionalParameters.Count != paramTypeOIDs.Count)
{
- var param = batchCommand.PositionalParameters[i];
- var paramOid = paramTypeOIDs[i];
+ connector.SkipUntil(BackendMessageCode.ReadyForQuery);
+ Parameters.Clear();
+ throw new NpgsqlException("There was a mismatch in the number of derived parameters between the Npgsql SQL parser and the PostgreSQL parser. Please report this as bug to the Npgsql developers (https://github.com/npgsql/npgsql/issues).");
+ }
- var (npgsqlDbType, postgresType) = connector.TypeMapper.GetTypeInfoByOid(paramOid);
+ for (var i = 0; i < paramTypeOIDs.Count; i++)
+ {
+ try
+ {
+ var param = batchCommand.PositionalParameters[i];
+ var paramOid = paramTypeOIDs[i];
+
+ var (npgsqlDbType, postgresType) = connector.TypeMapper.GetTypeInfoByOid(paramOid);
- if (param.NpgsqlDbType != NpgsqlDbType.Unknown && param.NpgsqlDbType != npgsqlDbType)
- throw new NpgsqlException("The backend parser inferred different types for parameters with the same name. Please try explicit casting within your SQL statement or batch or use different placeholder names.");
+ if (param.NpgsqlDbType != NpgsqlDbType.Unknown && param.NpgsqlDbType != npgsqlDbType)
+ throw new NpgsqlException("The backend parser inferred different types for parameters with the same name. Please try explicit casting within your SQL statement or batch or use different placeholder names.");
- param.DataTypeName = postgresType.DisplayName;
- param.PostgresType = postgresType;
- if (npgsqlDbType.HasValue)
- param.NpgsqlDbType = npgsqlDbType.Value;
+ param.DataTypeName = postgresType.DisplayName;
+ param.PostgresType = postgresType;
+ if (npgsqlDbType.HasValue)
+ param.NpgsqlDbType = npgsqlDbType.Value;
+ }
+ catch
+ {
+ connector.SkipUntil(BackendMessageCode.ReadyForQuery);
+ Parameters.Clear();
+ throw;
+ }
}
- catch
+
+ var msg = connector.ReadMessage(async: false).GetAwaiter().GetResult();
+ switch (msg.Code)
{
- connector.SkipUntil(BackendMessageCode.ReadyForQuery);
- Parameters.Clear();
- throw;
+ case BackendMessageCode.RowDescription:
+ case BackendMessageCode.NoData:
+ break;
+ default:
+ throw connector.UnexpectedMessageReceived(msg.Code);
}
}
- var msg = connector.ReadMessage(async: false).GetAwaiter().GetResult();
- switch (msg.Code)
+ Expect(connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector);
+ }
+ finally
+ {
+ try
{
- case BackendMessageCode.RowDescription:
- case BackendMessageCode.NoData:
- break;
- default:
- throw connector.UnexpectedMessageReceived(msg.Code);
+ // Make sure sendTask is complete so we don't race against asynchronous flush
+ sendTask.GetAwaiter().GetResult();
+ }
+ catch
+ {
+ // ignored
}
}
-
- Expect(connector.ReadMessage(async: false).GetAwaiter().GetResult(), connector);
- sendTask.GetAwaiter().GetResult();
}
}
@@ -646,6 +680,7 @@ Task Prepare(bool async, CancellationToken cancellationToken = default)
ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand);
needToPrepare = batchCommand.ExplicitPrepare(connector) || needToPrepare;
+ batchCommand.ConnectorPreparedOn = connector;
}
if (logger.IsEnabled(LogLevel.Debug) && needToPrepare)
@@ -681,53 +716,71 @@ static async Task PrepareLong(NpgsqlCommand command, bool async, NpgsqlConnector
if (sendTask.IsFaulted)
sendTask.GetAwaiter().GetResult();
- // Loop over statements, skipping those that are already prepared (because they were persisted)
- var isFirst = true;
- foreach (var batchCommand in command.InternalBatchCommands)
+ try
{
- if (!batchCommand.IsPreparing)
- continue;
+ // Loop over statements, skipping those that are already prepared (because they were persisted)
+ var isFirst = true;
+ foreach (var batchCommand in command.InternalBatchCommands)
+ {
+ if (!batchCommand.IsPreparing)
+ continue;
- var pStatement = batchCommand.PreparedStatement!;
+ var pStatement = batchCommand.PreparedStatement!;
+ var replacedStatement = pStatement.StatementBeingReplaced;
- if (pStatement.StatementBeingReplaced != null)
- {
- Expect(await connector.ReadMessage(async), connector);
- pStatement.StatementBeingReplaced.CompleteUnprepare();
- pStatement.StatementBeingReplaced = null;
+ if (replacedStatement != null)
+ {
+ Expect(await connector.ReadMessage(async), connector);
+ replacedStatement.CompleteUnprepare();
+
+ if (!replacedStatement.IsExplicit)
+ connector.PreparedStatementManager.AutoPrepared[replacedStatement.AutoPreparedSlotIndex] = null;
+
+ pStatement.StatementBeingReplaced = null;
+ }
+
+ Expect(await connector.ReadMessage(async), connector);
+ Expect(await connector.ReadMessage(async), connector);
+ var msg = await connector.ReadMessage(async);
+ switch (msg.Code)
+ {
+ case BackendMessageCode.RowDescription:
+ // Clone the RowDescription for use with the prepared statement (the one we have is reused
+ // by the connection)
+ var description = ((RowDescriptionMessage)msg).Clone();
+ command.FixupRowDescription(description, isFirst);
+ batchCommand.Description = description;
+ break;
+ case BackendMessageCode.NoData:
+ batchCommand.Description = null;
+ break;
+ default:
+ throw connector.UnexpectedMessageReceived(msg.Code);
+ }
+
+ pStatement.State = PreparedState.Prepared;
+ connector.PreparedStatementManager.NumPrepared++;
+ batchCommand.IsPreparing = false;
+ isFirst = false;
}
- Expect(await connector.ReadMessage(async), connector);
- Expect(await connector.ReadMessage(async), connector);
- var msg = await connector.ReadMessage(async);
- switch (msg.Code)
+ Expect(await connector.ReadMessage(async), connector);
+ }
+ finally
+ {
+ try
{
- case BackendMessageCode.RowDescription:
- // Clone the RowDescription for use with the prepared statement (the one we have is reused
- // by the connection)
- var description = ((RowDescriptionMessage)msg).Clone();
- command.FixupRowDescription(description, isFirst);
- batchCommand.Description = description;
- break;
- case BackendMessageCode.NoData:
- batchCommand.Description = null;
- break;
- default:
- throw connector.UnexpectedMessageReceived(msg.Code);
+ // Make sure sendTask is complete so we don't race against asynchronous flush
+ if (async)
+ await sendTask;
+ else
+ sendTask.GetAwaiter().GetResult();
+ }
+ catch
+ {
+ // ignored
}
-
- pStatement.State = PreparedState.Prepared;
- connector.PreparedStatementManager.NumPrepared++;
- batchCommand.IsPreparing = false;
- isFirst = false;
}
-
- Expect(await connector.ReadMessage(async), connector);
-
- if (async)
- await sendTask;
- else
- sendTask.GetAwaiter().GetResult();
}
LogMessages.CommandPreparedExplicitly(connector.CommandLogger, connector.Id);
@@ -786,9 +839,8 @@ async Task Unprepare(bool async, CancellationToken cancellationToken = default)
using (connector.StartUserAction(cancellationToken))
{
- var sendTask = SendClose(connector, async, cancellationToken);
- if (sendTask.IsFaulted)
- sendTask.GetAwaiter().GetResult();
+ // Just wait for SendClose to complete since each statement takes no more than 20 bytes
+ await SendClose(connector, async, cancellationToken);
foreach (var batchCommand in InternalBatchCommands)
{
@@ -807,11 +859,6 @@ async Task Unprepare(bool async, CancellationToken cancellationToken = default)
}
Expect(await connector.ReadMessage(async), connector);
-
- if (async)
- await sendTask;
- else
- sendTask.GetAwaiter().GetResult();
}
}
@@ -902,7 +949,7 @@ internal void ProcessRawQuery(SqlQueryParser? parser, bool standardConformingStr
case CommandType.StoredProcedure:
var sqlBuilder = new StringBuilder()
.Append(EnableStoredProcedureCompatMode ? "SELECT * FROM " : "CALL ")
- .Append(CommandText)
+ .Append(commandText)
.Append('(');
var isFirstParam = true;
@@ -1134,14 +1181,11 @@ async Task SendClose(NpgsqlConnector connector, bool async, CancellationToken ca
{
BeginSend(connector);
- var i = 0;
foreach (var batchCommand in InternalBatchCommands.Where(s => s.IsPrepared))
{
- ForceAsyncIfNecessary(ref async, i);
-
+ // No need to force async here since each statement takes no more than 20 bytes
await connector.WriteClose(StatementOrPortal.Statement, batchCommand.StatementName, async, cancellationToken);
batchCommand.PreparedStatement!.State = PreparedState.BeingUnprepared;
- i++;
}
await connector.WriteSync(async, cancellationToken);
@@ -1346,20 +1390,33 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior
{
case true:
Debug.Assert(_connectorPreparedOn != null);
- if (_connectorPreparedOn != connector)
- {
- // The command was prepared, but since then the connector has changed. Detach all prepared statements.
- foreach (var s in InternalBatchCommands)
- s.PreparedStatement = null;
- ResetPreparation();
- goto case false;
- }
-
if (IsWrappedByBatch)
+ {
foreach (var batchCommand in InternalBatchCommands)
+ {
+ if (batchCommand.ConnectorPreparedOn != connector)
+ {
+ foreach (var s in InternalBatchCommands)
+ s.ResetPreparation();
+ ResetPreparation();
+ goto case false;
+ }
+
batchCommand.Parameters.ProcessParameters(dataSource.TypeMapper, validateParameterValues, CommandType);
+ }
+ }
else
+ {
+ if (_connectorPreparedOn != connector)
+ {
+ // The command was prepared, but since then the connector has changed. Detach all prepared statements.
+ foreach (var s in InternalBatchCommands)
+ s.PreparedStatement = null;
+ ResetPreparation();
+ goto case false;
+ }
Parameters.ProcessParameters(dataSource.TypeMapper, validateParameterValues, CommandType);
+ }
NpgsqlEventSource.Log.CommandStartPrepared();
break;
@@ -1377,7 +1434,10 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior
ProcessRawQuery(connector.SqlQueryParser, connector.UseConformingStrings, batchCommand);
if (connector.Settings.MaxAutoPrepare > 0 && batchCommand.TryAutoPrepare(connector))
+ {
+ batchCommand.ConnectorPreparedOn = connector;
numPrepared++;
+ }
}
}
else
@@ -1401,6 +1461,10 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior
break;
}
+ // If a cancellation is in progress, wait for it to "complete" before proceeding (#615)
+ // We do it before changing the state because we only allow sending cancellation request if State == InProgress
+ connector.ResetCancellation();
+
State = CommandState.InProgress;
if (logger.IsEnabled(LogLevel.Information))
@@ -1412,12 +1476,8 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior
}
NpgsqlEventSource.Log.CommandStart(CommandText);
- TraceCommandStart(connector);
-
- // If a cancellation is in progress, wait for it to "complete" before proceeding (#615)
- lock (connector.CancelLock)
- {
- }
+ TraceCommandStart(connector.Settings);
+ TraceCommandEnrich(connector);
// We do not wait for the entire send to complete before proceeding to reading -
// the sending continues in parallel with the user's reading. Waiting for the
@@ -1487,6 +1547,8 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior
State = CommandState.InProgress;
+ TraceCommandStart(conn.Settings);
+
// TODO: Experiment: do we want to wait on *writing* here, or on *reading*?
// Previous behavior was to wait on reading, which throw the exception from ExecuteReader (and not from
// the first read). But waiting on writing would allow us to do sync writing and async reading.
@@ -1570,7 +1632,7 @@ public override void Cancel()
if (connector is null)
return;
- connector.PerformUserCancellation();
+ connector.PerformImmediateUserCancellation();
}
#endregion Cancel
@@ -1592,6 +1654,9 @@ protected override void Dispose(bool disposing)
_commandText = string.Empty;
CommandType = CommandType.Text;
_parameters.Clear();
+ _timeout = null;
+ AllResultTypesAreUnknown = false;
+ Debug.Assert(_unknownResultTypeList is null);
InternalConnection.CachedCommand = this;
return;
}
@@ -1605,19 +1670,23 @@ protected override void Dispose(bool disposing)
#endregion Tracing
- internal void TraceCommandStart(NpgsqlConnector connector)
+ internal void TraceCommandStart(NpgsqlConnectionStringBuilder settings)
{
Debug.Assert(CurrentActivity is null);
if (NpgsqlActivitySource.IsEnabled)
- CurrentActivity = NpgsqlActivitySource.CommandStart(connector, CommandText);
+ CurrentActivity = NpgsqlActivitySource.CommandStart(settings, IsWrappedByBatch ? GetBatchFullCommandText() : CommandText, CommandType);
+ }
+
+ internal void TraceCommandEnrich(NpgsqlConnector connector)
+ {
+ if (CurrentActivity is not null)
+ NpgsqlActivitySource.Enrich(CurrentActivity, connector);
}
internal void TraceReceivedFirstResponse()
{
if (CurrentActivity is not null)
- {
NpgsqlActivitySource.ReceivedFirstResponse(CurrentActivity);
- }
}
internal void TraceCommandStop()
diff --git a/src/Npgsql/NpgsqlConnection.cs b/src/Npgsql/NpgsqlConnection.cs
index a14b07ee7e..ed03a213c8 100644
--- a/src/Npgsql/NpgsqlConnection.cs
+++ b/src/Npgsql/NpgsqlConnection.cs
@@ -856,13 +856,10 @@ async Task CloseAsync(bool async)
connector.Connection = null;
- // If pooled, close the connection and disconnect it from the resource manager but leave the
- // connector in an enlisted pending list in the pool. If another connection is opened within
+ // Close the connection and disconnect it from the resource manager but leave the
+ // connector in an enlisted pending list in the data source. If another connection is opened within
// the same transaction scope, we will reuse this connector to avoid escalating to a distributed
// transaction
- // If a *non-pooled* connection is being closed but is enlisted in an ongoing
- // TransactionScope, we do nothing - simply detach the connector from the connection and leave
- // it open. It will be closed when the TransactionScope is disposed.
_dataSource?.AddPendingEnlistedConnector(connector, EnlistedTransaction);
EnlistedTransaction = null;
diff --git a/src/Npgsql/NpgsqlDataReader.cs b/src/Npgsql/NpgsqlDataReader.cs
index c6fc499720..b5d37c37c3 100644
--- a/src/Npgsql/NpgsqlDataReader.cs
+++ b/src/Npgsql/NpgsqlDataReader.cs
@@ -563,6 +563,8 @@ async Task NextResult(bool async, bool isConsuming = false, CancellationTo
{
preparedStatement.State = PreparedState.Invalidated;
Command.ResetPreparation();
+ foreach (var s in Command.InternalBatchCommands)
+ s.ResetPreparation();
}
}
@@ -726,6 +728,12 @@ async Task NextResultSchemaOnly(bool async, bool isConsuming = false, Canc
default:
throw Connector.UnexpectedMessageReceived(msg.Code);
}
+
+ if (_statements.Skip(StatementIndex + 1).All(x => x.IsPrepared))
+ {
+ // There are no more queries, we're done. Read to the RFQ.
+ Expect(await Connector.ReadMessage(async), Connector);
+ }
}
// Found a resultset
@@ -733,13 +741,8 @@ async Task NextResultSchemaOnly(bool async, bool isConsuming = false, Canc
return true;
}
- // There are no more queries, we're done. Read to the RFQ.
- if (!_statements.All(s => s.IsPrepared))
- {
- Expect(await Connector.ReadMessage(async), Connector);
- RowDescription = null;
- State = ReaderState.Consumed;
- }
+ RowDescription = null;
+ State = ReaderState.Consumed;
return false;
}
@@ -1160,17 +1163,28 @@ internal async Task Cleanup(bool async, bool connectionClosing = false, bool isD
// on .NET Framework.
if (_sendTask != null)
{
- try
+ // If the connector is broken, we have no reason to wait for the sendTask to complete
+ // as we're not going to send anything else over it
+ // and that can lead to deadlocks (concurrent write and read failure, see #4804)
+ if (Connector.IsBroken)
{
- if (async)
- await _sendTask;
- else
- _sendTask.GetAwaiter().GetResult();
+ // Prevent unobserved Task notifications by observing the failed Task exception.
+ _ = _sendTask.ContinueWith(t => _ = t.Exception, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Current);
}
- catch (Exception e)
+ else
{
- // TODO: think of a better way to handle exceptions, see #1323 and #3163
- _commandLogger.LogDebug(e, "Exception caught while sending the request", Connector.Id);
+ try
+ {
+ if (async)
+ await _sendTask;
+ else
+ _sendTask.GetAwaiter().GetResult();
+ }
+ catch (Exception e)
+ {
+ // TODO: think of a better way to handle exceptions, see #1323 and #3163
+ _commandLogger.LogDebug(e, "Exception caught while sending the request", Connector.Id);
+ }
}
}
@@ -2155,6 +2169,7 @@ Task> GetColumnSchema(bool async, Cancellatio
row["IsRowVersion"] = false;
row["IsHidden"] = column.IsHidden == true;
row["IsLong"] = column.IsLong == true;
+ row["IsReadOnly"] = column.IsReadOnly == true;
row["DataTypeName"] = column.DataTypeName;
table.Rows.Add(row);
diff --git a/src/Npgsql/NpgsqlDataSource.cs b/src/Npgsql/NpgsqlDataSource.cs
index 170c103a6b..754050da02 100644
--- a/src/Npgsql/NpgsqlDataSource.cs
+++ b/src/Npgsql/NpgsqlDataSource.cs
@@ -120,15 +120,11 @@ internal NpgsqlDataSource(
}
}
- ///
- /// Returns a new, unopened connection from this data source.
- ///
+ ///
public new NpgsqlConnection CreateConnection()
=> NpgsqlConnection.FromDataSource(this);
- ///
- /// Returns a new, opened connection from this data source.
- ///
+ ///
public new NpgsqlConnection OpenConnection()
{
var connection = CreateConnection();
@@ -145,12 +141,11 @@ internal NpgsqlDataSource(
}
}
- ///
- /// Returns a new, opened connection from this data source.
- ///
- ///
- /// An optional token to cancel the asynchronous operation. The default value is .
- ///
+ ///
+ protected override DbConnection OpenDbConnection()
+ => OpenConnection();
+
+ ///
public new async ValueTask OpenConnectionAsync(CancellationToken cancellationToken = default)
{
var connection = CreateConnection();
@@ -167,13 +162,17 @@ internal NpgsqlDataSource(
}
}
+ ///
+ protected override async ValueTask OpenDbConnectionAsync(CancellationToken cancellationToken = default)
+ => await OpenConnectionAsync(cancellationToken);
+
///
protected override DbConnection CreateDbConnection()
=> CreateConnection();
///
protected override DbCommand CreateDbCommand(string? commandText = null)
- => CreateCommand();
+ => CreateCommand(commandText);
///
protected override DbBatch CreateDbBatch()
diff --git a/src/Npgsql/NpgsqlDataSourceBuilder.cs b/src/Npgsql/NpgsqlDataSourceBuilder.cs
index 9cc6eebf8a..ca589035d8 100644
--- a/src/Npgsql/NpgsqlDataSourceBuilder.cs
+++ b/src/Npgsql/NpgsqlDataSourceBuilder.cs
@@ -339,19 +339,20 @@ public NpgsqlDataSourceBuilder UsePhysicalConnectionInitializer(
public NpgsqlDataSource Build()
{
var config = PrepareConfiguration();
+ var connectionStringBuilder = ConnectionStringBuilder.Clone();
if (ConnectionStringBuilder.Host!.Contains(","))
{
ValidateMultiHost();
- return new NpgsqlMultiHostDataSource(ConnectionStringBuilder, config);
+ return new NpgsqlMultiHostDataSource(connectionStringBuilder, config);
}
return ConnectionStringBuilder.Multiplexing
- ? new MultiplexingDataSource(ConnectionStringBuilder, config)
+ ? new MultiplexingDataSource(connectionStringBuilder, config)
: ConnectionStringBuilder.Pooling
- ? new PoolingDataSource(ConnectionStringBuilder, config)
- : new UnpooledDataSource(ConnectionStringBuilder, config);
+ ? new PoolingDataSource(connectionStringBuilder, config)
+ : new UnpooledDataSource(connectionStringBuilder, config);
}
///
@@ -363,7 +364,7 @@ public NpgsqlMultiHostDataSource BuildMultiHost()
ValidateMultiHost();
- return new(ConnectionStringBuilder, config);
+ return new(ConnectionStringBuilder.Clone(), config);
}
NpgsqlDataSourceConfiguration PrepareConfiguration()
diff --git a/src/Npgsql/NpgsqlMultiHostDataSource.cs b/src/Npgsql/NpgsqlMultiHostDataSource.cs
index 6762de9ad4..86b902c577 100644
--- a/src/Npgsql/NpgsqlMultiHostDataSource.cs
+++ b/src/Npgsql/NpgsqlMultiHostDataSource.cs
@@ -49,7 +49,7 @@ internal NpgsqlMultiHostDataSource(NpgsqlConnectionStringBuilder settings, Npgsq
poolSettings.Host = host.ToString();
_pools[i] = settings.Pooling
- ? new PoolingDataSource(poolSettings, dataSourceConfig, this)
+ ? new PoolingDataSource(poolSettings, dataSourceConfig)
: new UnpooledDataSource(poolSettings, dataSourceConfig);
}
diff --git a/src/Npgsql/NpgsqlRawCopyStream.cs b/src/Npgsql/NpgsqlRawCopyStream.cs
index c0ef7989db..79c8ca6638 100644
--- a/src/Npgsql/NpgsqlRawCopyStream.cs
+++ b/src/Npgsql/NpgsqlRawCopyStream.cs
@@ -143,25 +143,17 @@ public override void Write(ReadOnlySpan buffer)
return;
}
- try
- {
- // Value is too big, flush.
- Flush();
-
- if (buffer.Length <= _writeBuf.WriteSpaceLeft)
- {
- _writeBuf.WriteBytes(buffer);
- return;
- }
+ // Value is too big, flush.
+ Flush();
- // Value is too big even after a flush - bypass the buffer and write directly.
- _writeBuf.DirectWrite(buffer);
- }
- catch (Exception e)
+ if (buffer.Length <= _writeBuf.WriteSpaceLeft)
{
- _connector.Break(e);
- throw;
+ _writeBuf.WriteBytes(buffer);
+ return;
}
+
+ // Value is too big even after a flush - bypass the buffer and write directly.
+ _writeBuf.DirectWrite(buffer);
}
#if NETSTANDARD2_0
@@ -188,25 +180,17 @@ async ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToke
return;
}
- try
- {
- // Value is too big, flush.
- await FlushAsync(true, cancellationToken);
-
- if (buffer.Length <= _writeBuf.WriteSpaceLeft)
- {
- _writeBuf.WriteBytes(buffer.Span);
- return;
- }
+ // Value is too big, flush.
+ await FlushAsync(true, cancellationToken);
- // Value is too big even after a flush - bypass the buffer and write directly.
- await _writeBuf.DirectWrite(buffer, true, cancellationToken);
- }
- catch (Exception e)
+ if (buffer.Length <= _writeBuf.WriteSpaceLeft)
{
- _connector.Break(e);
- throw;
+ _writeBuf.WriteBytes(buffer.Span);
+ return;
}
+
+ // Value is too big even after a flush - bypass the buffer and write directly.
+ await _writeBuf.DirectWrite(buffer, true, cancellationToken);
}
}
@@ -581,4 +565,4 @@ public ValueTask DisposeAsync()
Dispose();
return default;
}
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/NpgsqlSchema.cs b/src/Npgsql/NpgsqlSchema.cs
index 75c5e857dc..e8d65ecbf1 100644
--- a/src/Npgsql/NpgsqlSchema.cs
+++ b/src/Npgsql/NpgsqlSchema.cs
@@ -293,7 +293,7 @@ static async Task GetUsers(NpgsqlConnection conn, string?[]? restrict
{
var users = new DataTable("Users") { Locale = CultureInfo.InvariantCulture };
- users.Columns.AddRange(new[] { new DataColumn("user_name"), new DataColumn("user_sysid", typeof(int)) });
+ users.Columns.AddRange(new[] { new DataColumn("user_name"), new DataColumn("user_sysid", typeof(uint)) });
var getUsers = new StringBuilder();
diff --git a/src/Npgsql/NpgsqlTransaction.cs b/src/Npgsql/NpgsqlTransaction.cs
index 0f0cb20fc6..d62ea134fb 100644
--- a/src/Npgsql/NpgsqlTransaction.cs
+++ b/src/Npgsql/NpgsqlTransaction.cs
@@ -230,16 +230,7 @@ public void Save(string name)
// Note: savepoint names are PostgreSQL identifiers, and so limited by default to 63 characters.
// Since we are prepending, we assume below that the statement will always fit in the buffer.
- _connector.WriteBuffer.WriteByte(FrontendMessageCode.Query);
- _connector.WriteBuffer.WriteInt32(
- sizeof(int) + // Message length (including self excluding code)
- _connector.TextEncoding.GetByteCount("SAVEPOINT ") +
- _connector.TextEncoding.GetByteCount(name) +
- sizeof(byte)); // Null terminator
-
- _connector.WriteBuffer.WriteString("SAVEPOINT ");
- _connector.WriteBuffer.WriteString(name);
- _connector.WriteBuffer.WriteByte(0);
+ _connector.WriteQuery("SAVEPOINT " + name);
_connector.PendingPrependedResponses += 2;
}
@@ -356,6 +347,18 @@ public Task ReleaseAsync(string name, CancellationToken cancellationToken = defa
return Release(name, true, cancellationToken);
}
+ ///
+ /// Indicates whether this transaction supports database savepoints.
+ ///
+#if NET5_0_OR_GREATER
+ public override bool SupportsSavepoints
+#else
+ public bool SupportsSavepoints
+#endif
+ {
+ get => _connector.DatabaseInfo.SupportsTransactions;
+ }
+
#endregion
#region Dispose
@@ -500,4 +503,4 @@ internal void UnbindIfNecessary()
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/src/Npgsql/NpgsqlTypes/NpgsqlTypes.cs b/src/Npgsql/NpgsqlTypes/NpgsqlTypes.cs
index 4a6c4d112b..0c246633c7 100644
--- a/src/Npgsql/NpgsqlTypes/NpgsqlTypes.cs
+++ b/src/Npgsql/NpgsqlTypes/NpgsqlTypes.cs
@@ -233,7 +233,10 @@ public struct NpgsqlPath : IList, IEquatable
readonly List _points;
public bool Open { get; set; }
- public NpgsqlPath(IEnumerable points, bool open) : this()
+ public NpgsqlPath()
+ => _points = new();
+
+ public NpgsqlPath(IEnumerable points, bool open)
{
_points = new List(points);
Open = open;
@@ -354,12 +357,15 @@ public struct NpgsqlPolygon : IList, IEquatable
{
readonly List _points;
+ public NpgsqlPolygon()
+ => _points = new();
+
public NpgsqlPolygon(IEnumerable points)
{
_points = new List(points);
}
- public NpgsqlPolygon(params NpgsqlPoint[] points) : this ((IEnumerable) points) {}
+ public NpgsqlPolygon(params NpgsqlPoint[] points) : this((IEnumerable) points) {}
public NpgsqlPolygon(int capacity)
{
diff --git a/src/Npgsql/PoolingDataSource.cs b/src/Npgsql/PoolingDataSource.cs
index f6a87c9e9b..0593eed6be 100644
--- a/src/Npgsql/PoolingDataSource.cs
+++ b/src/Npgsql/PoolingDataSource.cs
@@ -30,8 +30,6 @@ class PoolingDataSource : NpgsqlDataSource
///
private protected readonly NpgsqlConnector?[] Connectors;
- readonly NpgsqlMultiHostDataSource? _parentPool;
-
///
/// Reader side for the idle connector channel. Contains nulls in order to release waiting attempts after
/// a connector has been physically closed/broken.
@@ -77,15 +75,12 @@ internal sealed override (int Total, int Idle, int Busy) Statistics
internal PoolingDataSource(
NpgsqlConnectionStringBuilder settings,
- NpgsqlDataSourceConfiguration dataSourceConfig,
- NpgsqlMultiHostDataSource? parentPool = null)
+ NpgsqlDataSourceConfiguration dataSourceConfig)
: base(settings, dataSourceConfig)
{
if (settings.MaxPoolSize < settings.MinPoolSize)
throw new ArgumentException($"Connection can't have 'Max Pool Size' {settings.MaxPoolSize} under 'Min Pool Size' {settings.MinPoolSize}");
- _parentPool = parentPool;
-
// We enforce Max Pool Size, so no need to to create a bounded channel (which is less efficient)
// On the consuming side, we have the multiplexing write loop but also non-multiplexing Rents
// On the producing side, we have connections being released back into the pool (both multiplexing and not)
@@ -380,11 +375,6 @@ void CloseConnector(NpgsqlConnector connector)
UpdatePruningTimer();
}
- internal override bool TryRemovePendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction)
- => _parentPool is null
- ? base.TryRemovePendingEnlistedConnector(connector, transaction)
- : _parentPool.TryRemovePendingEnlistedConnector(connector, transaction);
-
#region Pruning
void UpdatePruningTimer()
@@ -439,10 +429,9 @@ static void PruneIdleConnectors(object? state)
connector != null)
{
if (pool.CheckIdleConnector(connector))
- {
pool.CloseConnector(connector);
- toPrune--;
- }
+
+ toPrune--;
}
}
diff --git a/src/Npgsql/PostgresDatabaseInfo.cs b/src/Npgsql/PostgresDatabaseInfo.cs
index a8a82fccd4..4d640fb261 100644
--- a/src/Npgsql/PostgresDatabaseInfo.cs
+++ b/src/Npgsql/PostgresDatabaseInfo.cs
@@ -299,7 +299,6 @@ static string SanitizeForReplicationConnection(string str)
}
await conn.Flush(async);
var byOID = new Dictionary();
- var buf = conn.ReadBuffer;
// First read the PostgreSQL version
Expect(await conn.ReadMessage(async), conn);
@@ -307,8 +306,10 @@ static string SanitizeForReplicationConnection(string str)
// We read the message in non-sequential mode which buffers the whole message.
// There is no need to ensure data within the message boundaries
Expect(await conn.ReadMessage(async), conn);
- buf.Skip(2); // Column count
- LongVersion = ReadNonNullableString(buf);
+ // Note that here and below we don't assign ReadBuffer to a variable
+ // because we might allocate oversize buffer
+ conn.ReadBuffer.Skip(2); // Column count
+ LongVersion = ReadNonNullableString(conn.ReadBuffer);
Expect(await conn.ReadMessage(async), conn);
if (isReplicationConnection)
Expect(await conn.ReadMessage(async), conn);
@@ -322,15 +323,15 @@ static string SanitizeForReplicationConnection(string str)
if (msg is not DataRowMessage)
break;
- buf.Skip(2); // Column count
- var nspname = ReadNonNullableString(buf);
- var oid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo);
+ conn.ReadBuffer.Skip(2); // Column count
+ var nspname = ReadNonNullableString(conn.ReadBuffer);
+ var oid = uint.Parse(ReadNonNullableString(conn.ReadBuffer), NumberFormatInfo.InvariantInfo);
Debug.Assert(oid != 0);
- var typname = ReadNonNullableString(buf);
- var typtype = ReadNonNullableString(buf)[0];
- var typnotnull = ReadNonNullableString(buf)[0] == 't';
- var len = buf.ReadInt32();
- var elemtypoid = len == -1 ? 0 : uint.Parse(buf.ReadString(len), NumberFormatInfo.InvariantInfo);
+ var typname = ReadNonNullableString(conn.ReadBuffer);
+ var typtype = ReadNonNullableString(conn.ReadBuffer)[0];
+ var typnotnull = ReadNonNullableString(conn.ReadBuffer)[0] == 't';
+ var len = conn.ReadBuffer.ReadInt32();
+ var elemtypoid = len == -1 ? 0 : uint.Parse(conn.ReadBuffer.ReadString(len), NumberFormatInfo.InvariantInfo);
switch (typtype)
{
@@ -436,10 +437,10 @@ static string SanitizeForReplicationConnection(string str)
if (msg is not DataRowMessage)
break;
- buf.Skip(2); // Column count
- var oid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo);
- var attname = ReadNonNullableString(buf);
- var atttypid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo);
+ conn.ReadBuffer.Skip(2); // Column count
+ var oid = uint.Parse(ReadNonNullableString(conn.ReadBuffer), NumberFormatInfo.InvariantInfo);
+ var attname = ReadNonNullableString(conn.ReadBuffer);
+ var atttypid = uint.Parse(ReadNonNullableString(conn.ReadBuffer), NumberFormatInfo.InvariantInfo);
if (oid != currentOID)
{
@@ -498,9 +499,9 @@ static string SanitizeForReplicationConnection(string str)
if (msg is not DataRowMessage)
break;
- buf.Skip(2); // Column count
- var oid = uint.Parse(ReadNonNullableString(buf), NumberFormatInfo.InvariantInfo);
- var enumlabel = ReadNonNullableString(buf);
+ conn.ReadBuffer.Skip(2); // Column count
+ var oid = uint.Parse(ReadNonNullableString(conn.ReadBuffer), NumberFormatInfo.InvariantInfo);
+ var enumlabel = ReadNonNullableString(conn.ReadBuffer);
if (oid != currentOID)
{
currentOID = oid;
diff --git a/src/Npgsql/PreparedStatement.cs b/src/Npgsql/PreparedStatement.cs
index 015adc5dd3..e8e1d63d8c 100644
--- a/src/Npgsql/PreparedStatement.cs
+++ b/src/Npgsql/PreparedStatement.cs
@@ -24,7 +24,8 @@ sealed class PreparedStatement
internal PreparedState State { get; set; }
- internal bool IsPrepared => State == PreparedState.Prepared;
+ // Invalidated statement is still prepared and allocated on PG's side
+ internal bool IsPrepared => State is PreparedState.Prepared or PreparedState.Invalidated;
///
/// If true, the user explicitly requested this statement be prepared. It does not get closed as part of
diff --git a/src/Npgsql/PreparedStatementManager.cs b/src/Npgsql/PreparedStatementManager.cs
index c7f18c52e5..7a74c2035a 100644
--- a/src/Npgsql/PreparedStatementManager.cs
+++ b/src/Npgsql/PreparedStatementManager.cs
@@ -60,7 +60,8 @@ internal PreparedStatementManager(NpgsqlConnector connector)
if (BySql.TryGetValue(sql, out var pStatement))
{
Debug.Assert(pStatement.State != PreparedState.Unprepared);
- if (pStatement.IsExplicit)
+ // If statement is invalidated, fall through below where we replace it with another
+ if (pStatement.IsExplicit && pStatement.State != PreparedState.Invalidated)
{
// Great, we've found an explicit prepared statement.
// We just need to check that the parameter types correspond, since prepared statements are
@@ -77,8 +78,10 @@ internal PreparedStatementManager(NpgsqlConnector connector)
// Found a candidate for autopreparation. Remove it and prepare explicitly.
RemoveCandidate(pStatement);
break;
+ // The statement is invalidated. Just replace it with a new one.
+ case PreparedState.Invalidated:
+ // The statement has already been autoprepared. We need to "promote" it to explicit.
case PreparedState.Prepared:
- // The statement has already been autoprepared. We need to "promote" it to explicit.
statementBeingReplaced = pStatement;
break;
case PreparedState.Unprepared:
diff --git a/src/Npgsql/PublicAPI.Shipped.txt b/src/Npgsql/PublicAPI.Shipped.txt
index 79818d3afd..87a3c12c5d 100644
--- a/src/Npgsql/PublicAPI.Shipped.txt
+++ b/src/Npgsql/PublicAPI.Shipped.txt
@@ -1,6 +1,4 @@
#nullable enable
-abstract Npgsql.Logging.NpgsqlLogger.IsEnabled(Npgsql.Logging.NpgsqlLogLevel level) -> bool
-abstract Npgsql.Logging.NpgsqlLogger.Log(Npgsql.Logging.NpgsqlLogLevel level, int connectorId, string! msg, System.Exception? exception = null) -> void
abstract Npgsql.Replication.PgOutput.Messages.UpdateMessage.NewRow.get -> Npgsql.Replication.PgOutput.ReplicationTuple!
abstract NpgsqlTypes.NpgsqlTsQuery.Equals(NpgsqlTypes.NpgsqlTsQuery? other) -> bool
const Npgsql.NpgsqlConnection.DefaultPort = 5432 -> int
@@ -241,18 +239,6 @@ const Npgsql.PostgresErrorCodes.WindowingError = "42P20" -> string!
const Npgsql.PostgresErrorCodes.WithCheckOptionViolation = "44000" -> string!
const Npgsql.PostgresErrorCodes.WrongObjectType = "42809" -> string!
const Npgsql.PostgresErrorCodes.ZeroLengthCharacterString = "2200F" -> string!
-const NpgsqlTypes.NpgsqlDate.MaxYear = 5874897 -> int
-const NpgsqlTypes.NpgsqlDate.MinYear = -4714 -> int
-const NpgsqlTypes.NpgsqlTimeSpan.DaysPerMonth = 30 -> int
-const NpgsqlTypes.NpgsqlTimeSpan.HoursPerDay = 24 -> int
-const NpgsqlTypes.NpgsqlTimeSpan.MonthsPerYear = 12 -> int
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerDay = 864000000000 -> long
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerHour = 36000000000 -> long
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMicrosecond = 10 -> long
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMillsecond = 10000 -> long
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMinute = 600000000 -> long
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMonth = 25920000000000 -> long
-const NpgsqlTypes.NpgsqlTimeSpan.TicksPerSecond = 10000000 -> long
Npgsql.ArrayNullabilityMode
Npgsql.ArrayNullabilityMode.Always = 1 -> Npgsql.ArrayNullabilityMode
Npgsql.ArrayNullabilityMode.Never = 0 -> Npgsql.ArrayNullabilityMode
@@ -265,21 +251,6 @@ Npgsql.BackendMessages.FieldDescription.TypeSize.set -> void
Npgsql.INpgsqlNameTranslator
Npgsql.INpgsqlNameTranslator.TranslateMemberName(string! clrName) -> string!
Npgsql.INpgsqlNameTranslator.TranslateTypeName(string! clrName) -> string!
-Npgsql.Logging.ConsoleLoggingProvider
-Npgsql.Logging.ConsoleLoggingProvider.ConsoleLoggingProvider(Npgsql.Logging.NpgsqlLogLevel minLevel = Npgsql.Logging.NpgsqlLogLevel.Info, bool printLevel = false, bool printConnectorId = false) -> void
-Npgsql.Logging.ConsoleLoggingProvider.CreateLogger(string! name) -> Npgsql.Logging.NpgsqlLogger!
-Npgsql.Logging.INpgsqlLoggingProvider
-Npgsql.Logging.INpgsqlLoggingProvider.CreateLogger(string! name) -> Npgsql.Logging.NpgsqlLogger!
-Npgsql.Logging.NpgsqlLogger
-Npgsql.Logging.NpgsqlLogger.NpgsqlLogger() -> void
-Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogLevel.Debug = 2 -> Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogLevel.Error = 5 -> Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogLevel.Fatal = 6 -> Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogLevel.Info = 3 -> Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogLevel.Trace = 1 -> Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogLevel.Warn = 4 -> Npgsql.Logging.NpgsqlLogLevel
-Npgsql.Logging.NpgsqlLogManager
Npgsql.NameTranslation.NpgsqlNullNameTranslator
Npgsql.NameTranslation.NpgsqlNullNameTranslator.NpgsqlNullNameTranslator() -> void
Npgsql.NameTranslation.NpgsqlNullNameTranslator.TranslateMemberName(string! clrName) -> string!
@@ -295,6 +266,8 @@ Npgsql.NpgsqlBatch
Npgsql.NpgsqlBatch.BatchCommands.get -> Npgsql.NpgsqlBatchCommandCollection!
Npgsql.NpgsqlBatch.Connection.get -> Npgsql.NpgsqlConnection?
Npgsql.NpgsqlBatch.Connection.set -> void
+Npgsql.NpgsqlBatch.EnableErrorBarriers.get -> bool
+Npgsql.NpgsqlBatch.EnableErrorBarriers.set -> void
Npgsql.NpgsqlBatch.ExecuteReader(System.Data.CommandBehavior behavior = System.Data.CommandBehavior.Default) -> Npgsql.NpgsqlDataReader!
Npgsql.NpgsqlBatch.ExecuteReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Npgsql.NpgsqlBatch.ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
@@ -302,6 +275,8 @@ Npgsql.NpgsqlBatch.NpgsqlBatch(Npgsql.NpgsqlConnection? connection = null, Npgsq
Npgsql.NpgsqlBatch.Transaction.get -> Npgsql.NpgsqlTransaction?
Npgsql.NpgsqlBatch.Transaction.set -> void
Npgsql.NpgsqlBatchCommand
+Npgsql.NpgsqlBatchCommand.AppendErrorBarrier.get -> bool?
+Npgsql.NpgsqlBatchCommand.AppendErrorBarrier.set -> void
Npgsql.NpgsqlBatchCommand.NpgsqlBatchCommand() -> void
Npgsql.NpgsqlBatchCommand.NpgsqlBatchCommand(string! commandText) -> void
Npgsql.NpgsqlBatchCommand.OID.get -> uint
@@ -355,7 +330,6 @@ Npgsql.NpgsqlBinaryImporter.WriteRowAsync(System.Threading.CancellationToken can
Npgsql.NpgsqlCommand
Npgsql.NpgsqlCommand.AllResultTypesAreUnknown.get -> bool
Npgsql.NpgsqlCommand.AllResultTypesAreUnknown.set -> void
-Npgsql.NpgsqlCommand.Clone() -> Npgsql.NpgsqlCommand!
Npgsql.NpgsqlCommand.Connection.get -> Npgsql.NpgsqlConnection?
Npgsql.NpgsqlCommand.Connection.set -> void
Npgsql.NpgsqlCommand.CreateParameter() -> Npgsql.NpgsqlParameter!
@@ -409,16 +383,10 @@ Npgsql.NpgsqlConnection.FullState.get -> System.Data.ConnectionState
Npgsql.NpgsqlConnection.HasIntegerDateTimes.get -> bool
Npgsql.NpgsqlConnection.Host.get -> string?
Npgsql.NpgsqlConnection.IntegratedSecurity.get -> bool
-Npgsql.NpgsqlConnection.MapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-Npgsql.NpgsqlConnection.MapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
Npgsql.NpgsqlConnection.Notice -> Npgsql.NoticeEventHandler?
Npgsql.NpgsqlConnection.Notification -> Npgsql.NotificationEventHandler?
Npgsql.NpgsqlConnection.NpgsqlConnection() -> void
Npgsql.NpgsqlConnection.NpgsqlConnection(string? connectionString) -> void
-Npgsql.NpgsqlConnection.PhysicalOpenAsyncCallback.get -> Npgsql.PhysicalOpenAsyncCallback?
-Npgsql.NpgsqlConnection.PhysicalOpenAsyncCallback.set -> void
-Npgsql.NpgsqlConnection.PhysicalOpenCallback.get -> Npgsql.PhysicalOpenCallback?
-Npgsql.NpgsqlConnection.PhysicalOpenCallback.set -> void
Npgsql.NpgsqlConnection.Port.get -> int
Npgsql.NpgsqlConnection.PostgresParameters.get -> System.Collections.Generic.IReadOnlyDictionary!
Npgsql.NpgsqlConnection.PostgreSqlVersion.get -> System.Version!
@@ -428,7 +396,7 @@ Npgsql.NpgsqlConnection.ProvideClientCertificatesCallback.set -> void
Npgsql.NpgsqlConnection.ProvidePasswordCallback.get -> Npgsql.ProvidePasswordCallback?
Npgsql.NpgsqlConnection.ProvidePasswordCallback.set -> void
Npgsql.NpgsqlConnection.ReloadTypes() -> void
-Npgsql.NpgsqlConnection.Settings.get -> Npgsql.NpgsqlConnectionStringBuilder!
+Npgsql.NpgsqlConnection.ReloadTypesAsync() -> System.Threading.Tasks.Task!
Npgsql.NpgsqlConnection.Timezone.get -> string!
Npgsql.NpgsqlConnection.TypeMapper.get -> Npgsql.TypeMapping.INpgsqlTypeMapper!
Npgsql.NpgsqlConnection.UnprepareAll() -> void
@@ -586,10 +554,6 @@ Npgsql.NpgsqlConnectionStringBuilder.WriteBufferSize.get -> int
Npgsql.NpgsqlConnectionStringBuilder.WriteBufferSize.set -> void
Npgsql.NpgsqlConnectionStringBuilder.WriteCoalescingBufferThresholdBytes.get -> int
Npgsql.NpgsqlConnectionStringBuilder.WriteCoalescingBufferThresholdBytes.set -> void
-Npgsql.NpgsqlConnectionStringPropertyAttribute
-Npgsql.NpgsqlConnectionStringPropertyAttribute.NpgsqlConnectionStringPropertyAttribute() -> void
-Npgsql.NpgsqlConnectionStringPropertyAttribute.NpgsqlConnectionStringPropertyAttribute(params string![]! synonyms) -> void
-Npgsql.NpgsqlConnectionStringPropertyAttribute.Synonyms.get -> string![]!
Npgsql.NpgsqlCopyTextReader
Npgsql.NpgsqlCopyTextReader.Cancel() -> void
Npgsql.NpgsqlCopyTextReader.CancelAsync() -> System.Threading.Tasks.Task!
@@ -617,17 +581,44 @@ Npgsql.NpgsqlDataReader.GetColumnSchema() -> System.Collections.ObjectModel.Read
Npgsql.NpgsqlDataReader.GetColumnSchemaAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>!
Npgsql.NpgsqlDataReader.GetData(int ordinal) -> Npgsql.NpgsqlNestedDataReader!
Npgsql.NpgsqlDataReader.GetDataTypeOID(int ordinal) -> uint
-Npgsql.NpgsqlDataReader.GetDate(int ordinal) -> NpgsqlTypes.NpgsqlDate
-Npgsql.NpgsqlDataReader.GetInterval(int ordinal) -> NpgsqlTypes.NpgsqlTimeSpan
Npgsql.NpgsqlDataReader.GetPostgresType(int ordinal) -> Npgsql.PostgresTypes.PostgresType!
Npgsql.NpgsqlDataReader.GetStreamAsync(int ordinal, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Npgsql.NpgsqlDataReader.GetTextReaderAsync(int ordinal, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Npgsql.NpgsqlDataReader.GetTimeSpan(int ordinal) -> System.TimeSpan
-Npgsql.NpgsqlDataReader.GetTimeStamp(int ordinal) -> NpgsqlTypes.NpgsqlDateTime
Npgsql.NpgsqlDataReader.IsOnRow.get -> bool
Npgsql.NpgsqlDataReader.ReaderClosed -> System.EventHandler?
Npgsql.NpgsqlDataReader.Rows.get -> ulong
Npgsql.NpgsqlDataReader.Statements.get -> System.Collections.Generic.IReadOnlyList!
+Npgsql.NpgsqlDataSource
+Npgsql.NpgsqlDataSource.CreateBatch() -> Npgsql.NpgsqlBatch!
+Npgsql.NpgsqlDataSource.CreateCommand(string? commandText = null) -> Npgsql.NpgsqlCommand!
+Npgsql.NpgsqlDataSource.CreateConnection() -> Npgsql.NpgsqlConnection!
+Npgsql.NpgsqlDataSource.OpenConnection() -> Npgsql.NpgsqlConnection!
+Npgsql.NpgsqlDataSource.OpenConnectionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
+Npgsql.NpgsqlDataSource.Password.set -> void
+Npgsql.NpgsqlDataSourceBuilder
+Npgsql.NpgsqlDataSourceBuilder.AddTypeResolverFactory(Npgsql.Internal.TypeHandling.TypeHandlerResolverFactory! resolverFactory) -> void
+Npgsql.NpgsqlDataSourceBuilder.Build() -> Npgsql.NpgsqlDataSource!
+Npgsql.NpgsqlDataSourceBuilder.BuildMultiHost() -> Npgsql.NpgsqlMultiHostDataSource!
+Npgsql.NpgsqlDataSourceBuilder.ConnectionString.get -> string!
+Npgsql.NpgsqlDataSourceBuilder.ConnectionStringBuilder.get -> Npgsql.NpgsqlConnectionStringBuilder!
+Npgsql.NpgsqlDataSourceBuilder.DefaultNameTranslator.get -> Npgsql.INpgsqlNameTranslator!
+Npgsql.NpgsqlDataSourceBuilder.DefaultNameTranslator.set -> void
+Npgsql.NpgsqlDataSourceBuilder.EnableParameterLogging(bool parameterLoggingEnabled = true) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.MapComposite(System.Type! clrType, string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
+Npgsql.NpgsqlDataSourceBuilder.MapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
+Npgsql.NpgsqlDataSourceBuilder.MapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
+Npgsql.NpgsqlDataSourceBuilder.NpgsqlDataSourceBuilder(string? connectionString = null) -> void
+Npgsql.NpgsqlDataSourceBuilder.UnmapComposite(System.Type! clrType, string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> bool
+Npgsql.NpgsqlDataSourceBuilder.UnmapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> bool
+Npgsql.NpgsqlDataSourceBuilder.UnmapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> bool
+Npgsql.NpgsqlDataSourceBuilder.UseClientCertificate(System.Security.Cryptography.X509Certificates.X509Certificate? clientCertificate) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.UseClientCertificates(System.Security.Cryptography.X509Certificates.X509CertificateCollection? clientCertificates) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.UseClientCertificatesCallback(System.Action? clientCertificatesCallback) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.UseLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory? loggerFactory) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.UsePeriodicPasswordProvider(System.Func>? passwordProvider, System.TimeSpan successRefreshInterval, System.TimeSpan failureRefreshInterval) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.UsePhysicalConnectionInitializer(System.Action? connectionInitializer, System.Func? connectionInitializerAsync) -> Npgsql.NpgsqlDataSourceBuilder!
+Npgsql.NpgsqlDataSourceBuilder.UseUserCertificateValidationCallback(System.Net.Security.RemoteCertificateValidationCallback! userCertificateValidationCallback) -> Npgsql.NpgsqlDataSourceBuilder!
Npgsql.NpgsqlException
Npgsql.NpgsqlException.BatchCommand.get -> Npgsql.NpgsqlBatchCommand?
Npgsql.NpgsqlException.BatchCommand.set -> void
@@ -659,6 +650,13 @@ Npgsql.NpgsqlLargeObjectStream.GetLengthAsync(System.Threading.CancellationToken
Npgsql.NpgsqlLargeObjectStream.Has64BitSupport.get -> bool
Npgsql.NpgsqlLargeObjectStream.SeekAsync(long offset, System.IO.SeekOrigin origin, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Npgsql.NpgsqlLargeObjectStream.SetLength(long value, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
+Npgsql.NpgsqlLoggingConfiguration
+Npgsql.NpgsqlMultiHostDataSource
+Npgsql.NpgsqlMultiHostDataSource.ClearDatabaseStates() -> void
+Npgsql.NpgsqlMultiHostDataSource.CreateConnection(Npgsql.TargetSessionAttributes targetSessionAttributes) -> Npgsql.NpgsqlConnection!
+Npgsql.NpgsqlMultiHostDataSource.OpenConnection(Npgsql.TargetSessionAttributes targetSessionAttributes) -> Npgsql.NpgsqlConnection!
+Npgsql.NpgsqlMultiHostDataSource.OpenConnectionAsync(Npgsql.TargetSessionAttributes targetSessionAttributes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
+Npgsql.NpgsqlMultiHostDataSource.WithTargetSession(Npgsql.TargetSessionAttributes targetSessionAttributes) -> Npgsql.NpgsqlDataSource!
Npgsql.NpgsqlNestedDataReader
Npgsql.NpgsqlNestedDataReader.GetData(int ordinal) -> Npgsql.NpgsqlNestedDataReader!
Npgsql.NpgsqlNoticeEventArgs
@@ -742,8 +740,6 @@ Npgsql.NpgsqlTracingOptions
Npgsql.NpgsqlTracingOptions.NpgsqlTracingOptions() -> void
Npgsql.NpgsqlTransaction
Npgsql.NpgsqlTransaction.Connection.get -> Npgsql.NpgsqlConnection?
-Npgsql.PhysicalOpenAsyncCallback
-Npgsql.PhysicalOpenCallback
Npgsql.PostgresErrorCodes
Npgsql.PostgresException
Npgsql.PostgresException.Code.get -> string!
@@ -859,6 +855,7 @@ Npgsql.Replication.LogicalSlotSnapshotInitMode.Use = 1 -> Npgsql.Replication.Log
Npgsql.Replication.PgOutput.Messages.BeginMessage
Npgsql.Replication.PgOutput.Messages.BeginMessage.TransactionCommitTimestamp.get -> System.DateTime
Npgsql.Replication.PgOutput.Messages.BeginMessage.TransactionFinalLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.BeginPrepareMessage
Npgsql.Replication.PgOutput.Messages.CommitMessage
Npgsql.Replication.PgOutput.Messages.CommitMessage.CommitFlags
Npgsql.Replication.PgOutput.Messages.CommitMessage.CommitFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.CommitMessage.CommitFlags
@@ -866,6 +863,13 @@ Npgsql.Replication.PgOutput.Messages.CommitMessage.CommitLsn.get -> NpgsqlTypes.
Npgsql.Replication.PgOutput.Messages.CommitMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.CommitMessage.CommitFlags
Npgsql.Replication.PgOutput.Messages.CommitMessage.TransactionCommitTimestamp.get -> System.DateTime
Npgsql.Replication.PgOutput.Messages.CommitMessage.TransactionEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags
+Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.TransactionCommitTimestamp.get -> System.DateTime
Npgsql.Replication.PgOutput.Messages.DefaultUpdateMessage
Npgsql.Replication.PgOutput.Messages.DeleteMessage
Npgsql.Replication.PgOutput.Messages.DeleteMessage.Relation.get -> Npgsql.Replication.PgOutput.Messages.RelationMessage!
@@ -892,6 +896,16 @@ Npgsql.Replication.PgOutput.Messages.OriginMessage.OriginCommitLsn.get -> Npgsql
Npgsql.Replication.PgOutput.Messages.OriginMessage.OriginName.get -> string!
Npgsql.Replication.PgOutput.Messages.PgOutputReplicationMessage
Npgsql.Replication.PgOutput.Messages.PgOutputReplicationMessage.PgOutputReplicationMessage() -> void
+Npgsql.Replication.PgOutput.Messages.PreparedTransactionControlMessage
+Npgsql.Replication.PgOutput.Messages.PreparedTransactionControlMessage.TransactionGid.get -> string!
+Npgsql.Replication.PgOutput.Messages.PrepareMessage
+Npgsql.Replication.PgOutput.Messages.PrepareMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags
+Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags
+Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags
+Npgsql.Replication.PgOutput.Messages.PrepareMessageBase
+Npgsql.Replication.PgOutput.Messages.PrepareMessageBase.PrepareEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.PrepareMessageBase.PrepareLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.PrepareMessageBase.TransactionPrepareTimestamp.get -> System.DateTime
Npgsql.Replication.PgOutput.Messages.RelationMessage
Npgsql.Replication.PgOutput.Messages.RelationMessage.Column
Npgsql.Replication.PgOutput.Messages.RelationMessage.Column.Column() -> void
@@ -918,6 +932,14 @@ Npgsql.Replication.PgOutput.Messages.RelationMessageColumn.DataTypeId.get -> uin
Npgsql.Replication.PgOutput.Messages.RelationMessageColumn.Flags.get -> byte
Npgsql.Replication.PgOutput.Messages.RelationMessageColumn.RelationMessageColumn() -> void
Npgsql.Replication.PgOutput.Messages.RelationMessageColumn.TypeModifier.get -> int
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.PreparedTransactionEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.TransactionPrepareTimestamp.get -> System.DateTime
+Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.TransactionRollbackTimestamp.get -> System.DateTime
Npgsql.Replication.PgOutput.Messages.StreamAbortMessage
Npgsql.Replication.PgOutput.Messages.StreamAbortMessage.SubtransactionXid.get -> uint
Npgsql.Replication.PgOutput.Messages.StreamCommitMessage
@@ -925,6 +947,10 @@ Npgsql.Replication.PgOutput.Messages.StreamCommitMessage.CommitLsn.get -> Npgsql
Npgsql.Replication.PgOutput.Messages.StreamCommitMessage.Flags.get -> byte
Npgsql.Replication.PgOutput.Messages.StreamCommitMessage.TransactionCommitTimestamp.get -> System.DateTime
Npgsql.Replication.PgOutput.Messages.StreamCommitMessage.TransactionEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
+Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage
+Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags
+Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags
+Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags
Npgsql.Replication.PgOutput.Messages.StreamStartMessage
Npgsql.Replication.PgOutput.Messages.StreamStartMessage.StreamSegmentIndicator.get -> byte
Npgsql.Replication.PgOutput.Messages.StreamStopMessage
@@ -952,11 +978,12 @@ Npgsql.Replication.PgOutput.PgOutputReplicationOptions
Npgsql.Replication.PgOutput.PgOutputReplicationOptions.Binary.get -> bool?
Npgsql.Replication.PgOutput.PgOutputReplicationOptions.Equals(Npgsql.Replication.PgOutput.PgOutputReplicationOptions? other) -> bool
Npgsql.Replication.PgOutput.PgOutputReplicationOptions.Messages.get -> bool?
-Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(string! publicationName, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null) -> void
-Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(System.Collections.Generic.IEnumerable! publicationNames, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null) -> void
+Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(string! publicationName, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null, bool? twoPhase = null) -> void
+Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(System.Collections.Generic.IEnumerable! publicationNames, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null, bool? twoPhase = null) -> void
Npgsql.Replication.PgOutput.PgOutputReplicationOptions.ProtocolVersion.get -> ulong
Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PublicationNames.get -> System.Collections.Generic.List!
Npgsql.Replication.PgOutput.PgOutputReplicationOptions.Streaming.get -> bool?
+Npgsql.Replication.PgOutput.PgOutputReplicationOptions.TwoPhase.get -> bool?
Npgsql.Replication.PgOutput.PgOutputReplicationSlot
Npgsql.Replication.PgOutput.PgOutputReplicationSlot.PgOutputReplicationSlot(Npgsql.Replication.PgOutput.PgOutputReplicationSlot! slot) -> void
Npgsql.Replication.PgOutput.PgOutputReplicationSlot.PgOutputReplicationSlot(Npgsql.Replication.ReplicationSlotOptions options) -> void
@@ -985,10 +1012,14 @@ Npgsql.Replication.PhysicalReplicationConnection
Npgsql.Replication.PhysicalReplicationConnection.CreateReplicationSlot(string! slotName, bool isTemporary = false, bool reserveWal = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Npgsql.Replication.PhysicalReplicationConnection.PhysicalReplicationConnection() -> void
Npgsql.Replication.PhysicalReplicationConnection.PhysicalReplicationConnection(string? connectionString) -> void
-Npgsql.Replication.PhysicalReplicationConnection.StartReplication(Npgsql.Replication.PhysicalReplicationSlot? slot, NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, uint timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
-Npgsql.Replication.PhysicalReplicationConnection.StartReplication(NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, uint timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
+Npgsql.Replication.PhysicalReplicationConnection.ReadReplicationSlot(string! slotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+Npgsql.Replication.PhysicalReplicationConnection.StartReplication(Npgsql.Replication.PhysicalReplicationSlot! slot, System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerable!
+Npgsql.Replication.PhysicalReplicationConnection.StartReplication(Npgsql.Replication.PhysicalReplicationSlot? slot, NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, ulong timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
+Npgsql.Replication.PhysicalReplicationConnection.StartReplication(NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, ulong timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
Npgsql.Replication.PhysicalReplicationSlot
-Npgsql.Replication.PhysicalReplicationSlot.PhysicalReplicationSlot(string! slotName) -> void
+Npgsql.Replication.PhysicalReplicationSlot.PhysicalReplicationSlot(string! slotName, NpgsqlTypes.NpgsqlLogSequenceNumber? restartLsn = null, ulong? restartTimeline = null) -> void
+Npgsql.Replication.PhysicalReplicationSlot.RestartLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber?
+Npgsql.Replication.PhysicalReplicationSlot.RestartTimeline.get -> ulong?
Npgsql.Replication.ReplicationConnection
Npgsql.Replication.ReplicationConnection.CommandTimeout.get -> System.TimeSpan
Npgsql.Replication.ReplicationConnection.CommandTimeout.set -> void
@@ -1087,6 +1118,8 @@ Npgsql.Schema.NpgsqlDbColumn.IsAliased.get -> bool?
Npgsql.Schema.NpgsqlDbColumn.IsAliased.set -> void
Npgsql.Schema.NpgsqlDbColumn.IsAutoIncrement.get -> bool?
Npgsql.Schema.NpgsqlDbColumn.IsAutoIncrement.set -> void
+Npgsql.Schema.NpgsqlDbColumn.IsIdentity.get -> bool?
+Npgsql.Schema.NpgsqlDbColumn.IsIdentity.set -> void
Npgsql.Schema.NpgsqlDbColumn.IsKey.get -> bool?
Npgsql.Schema.NpgsqlDbColumn.IsKey.set -> void
Npgsql.Schema.NpgsqlDbColumn.IsLong.get -> bool?
@@ -1118,6 +1151,7 @@ Npgsql.SslMode.Require = 3 -> Npgsql.SslMode
Npgsql.SslMode.VerifyCA = 4 -> Npgsql.SslMode
Npgsql.SslMode.VerifyFull = 5 -> Npgsql.SslMode
Npgsql.StatementType
+Npgsql.StatementType.Call = 11 -> Npgsql.StatementType
Npgsql.StatementType.Copy = 8 -> Npgsql.StatementType
Npgsql.StatementType.CreateTableAs = 5 -> Npgsql.StatementType
Npgsql.StatementType.Delete = 3 -> Npgsql.StatementType
@@ -1132,6 +1166,7 @@ Npgsql.StatementType.Update = 4 -> Npgsql.StatementType
Npgsql.TypeMapping.INpgsqlTypeMapper
Npgsql.TypeMapping.INpgsqlTypeMapper.AddTypeResolverFactory(Npgsql.Internal.TypeHandling.TypeHandlerResolverFactory! resolverFactory) -> void
Npgsql.TypeMapping.INpgsqlTypeMapper.DefaultNameTranslator.get -> Npgsql.INpgsqlNameTranslator!
+Npgsql.TypeMapping.INpgsqlTypeMapper.DefaultNameTranslator.set -> void
Npgsql.TypeMapping.INpgsqlTypeMapper.MapComposite(System.Type! clrType, string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
Npgsql.TypeMapping.INpgsqlTypeMapper.MapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
Npgsql.TypeMapping.INpgsqlTypeMapper.MapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
@@ -1170,78 +1205,6 @@ NpgsqlTypes.NpgsqlCircle.X.get -> double
NpgsqlTypes.NpgsqlCircle.X.set -> void
NpgsqlTypes.NpgsqlCircle.Y.get -> double
NpgsqlTypes.NpgsqlCircle.Y.set -> void
-NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDate.Add(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDate.AddDays(int days) -> NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDate.AddMonths(int months) -> NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDate.AddYears(int years) -> NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDate.Compare(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> int
-NpgsqlTypes.NpgsqlDate.Compare(object? x, object? y) -> int
-NpgsqlTypes.NpgsqlDate.CompareTo(NpgsqlTypes.NpgsqlDate other) -> int
-NpgsqlTypes.NpgsqlDate.CompareTo(object? o) -> int
-NpgsqlTypes.NpgsqlDate.Day.get -> int
-NpgsqlTypes.NpgsqlDate.DayOfWeek.get -> System.DayOfWeek
-NpgsqlTypes.NpgsqlDate.DayOfYear.get -> int
-NpgsqlTypes.NpgsqlDate.Equals(NpgsqlTypes.NpgsqlDate other) -> bool
-NpgsqlTypes.NpgsqlDate.IsFinite.get -> bool
-NpgsqlTypes.NpgsqlDate.IsInfinity.get -> bool
-NpgsqlTypes.NpgsqlDate.IsLeapYear.get -> bool
-NpgsqlTypes.NpgsqlDate.IsNegativeInfinity.get -> bool
-NpgsqlTypes.NpgsqlDate.Month.get -> int
-NpgsqlTypes.NpgsqlDate.NpgsqlDate() -> void
-NpgsqlTypes.NpgsqlDate.NpgsqlDate(int year, int month, int day) -> void
-NpgsqlTypes.NpgsqlDate.NpgsqlDate(NpgsqlTypes.NpgsqlDate copyFrom) -> void
-NpgsqlTypes.NpgsqlDate.NpgsqlDate(System.DateOnly date) -> void
-NpgsqlTypes.NpgsqlDate.NpgsqlDate(System.DateTime dateTime) -> void
-NpgsqlTypes.NpgsqlDate.Subtract(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDate.Year.get -> int
-NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.Add(in NpgsqlTypes.NpgsqlTimeSpan value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.Add(System.TimeSpan value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddDays(double value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddHours(double value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddMilliseconds(double value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddMinutes(double value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddMonths(int value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddSeconds(double value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddTicks(long value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.AddYears(int value) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.Compare(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> int
-NpgsqlTypes.NpgsqlDateTime.Compare(object? x, object? y) -> int
-NpgsqlTypes.NpgsqlDateTime.CompareTo(NpgsqlTypes.NpgsqlDateTime other) -> int
-NpgsqlTypes.NpgsqlDateTime.CompareTo(object? o) -> int
-NpgsqlTypes.NpgsqlDateTime.Date.get -> NpgsqlTypes.NpgsqlDate
-NpgsqlTypes.NpgsqlDateTime.Day.get -> int
-NpgsqlTypes.NpgsqlDateTime.DayOfWeek.get -> System.DayOfWeek
-NpgsqlTypes.NpgsqlDateTime.DayOfYear.get -> int
-NpgsqlTypes.NpgsqlDateTime.Equals(NpgsqlTypes.NpgsqlDateTime other) -> bool
-NpgsqlTypes.NpgsqlDateTime.Hour.get -> int
-NpgsqlTypes.NpgsqlDateTime.IsFinite.get -> bool
-NpgsqlTypes.NpgsqlDateTime.IsInfinity.get -> bool
-NpgsqlTypes.NpgsqlDateTime.IsLeapYear.get -> bool
-NpgsqlTypes.NpgsqlDateTime.IsNegativeInfinity.get -> bool
-NpgsqlTypes.NpgsqlDateTime.Kind.get -> System.DateTimeKind
-NpgsqlTypes.NpgsqlDateTime.Millisecond.get -> int
-NpgsqlTypes.NpgsqlDateTime.Minute.get -> int
-NpgsqlTypes.NpgsqlDateTime.Month.get -> int
-NpgsqlTypes.NpgsqlDateTime.Normalize() -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime() -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(int year, int month, int day, int hours, int minutes, int seconds, int milliseconds, System.DateTimeKind kind = System.DateTimeKind.Unspecified) -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(int year, int month, int day, int hours, int minutes, int seconds, System.DateTimeKind kind = System.DateTimeKind.Unspecified) -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(long ticks) -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(long ticks, System.DateTimeKind kind) -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(NpgsqlTypes.NpgsqlDate date) -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(NpgsqlTypes.NpgsqlDate date, System.TimeSpan time, System.DateTimeKind kind = System.DateTimeKind.Unspecified) -> void
-NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(System.DateTime dateTime) -> void
-NpgsqlTypes.NpgsqlDateTime.Second.get -> int
-NpgsqlTypes.NpgsqlDateTime.Subtract(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.Subtract(NpgsqlTypes.NpgsqlDateTime timestamp) -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlDateTime.Ticks.get -> long
-NpgsqlTypes.NpgsqlDateTime.Time.get -> System.TimeSpan
-NpgsqlTypes.NpgsqlDateTime.ToDateTime() -> System.DateTime
-NpgsqlTypes.NpgsqlDateTime.ToLocalTime() -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.ToUniversalTime() -> NpgsqlTypes.NpgsqlDateTime
-NpgsqlTypes.NpgsqlDateTime.Year.get -> int
NpgsqlTypes.NpgsqlDbType
NpgsqlTypes.NpgsqlDbType.Abstime = 33 -> NpgsqlTypes.NpgsqlDbType
NpgsqlTypes.NpgsqlDbType.Array = -2147483648 -> NpgsqlTypes.NpgsqlDbType
@@ -1436,46 +1399,6 @@ NpgsqlTypes.NpgsqlTid.Equals(NpgsqlTypes.NpgsqlTid other) -> bool
NpgsqlTypes.NpgsqlTid.NpgsqlTid() -> void
NpgsqlTypes.NpgsqlTid.NpgsqlTid(uint blockNumber, ushort offsetNumber) -> void
NpgsqlTypes.NpgsqlTid.OffsetNumber.get -> ushort
-NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.Add(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.Canonicalize() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.CompareTo(NpgsqlTypes.NpgsqlTimeSpan other) -> int
-NpgsqlTypes.NpgsqlTimeSpan.CompareTo(object? other) -> int
-NpgsqlTypes.NpgsqlTimeSpan.Days.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.Duration() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.Equals(NpgsqlTypes.NpgsqlTimeSpan other) -> bool
-NpgsqlTypes.NpgsqlTimeSpan.Hours.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.JustifyDays() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.JustifyInterval() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.JustifyMonths() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.Microseconds.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.Milliseconds.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.Minutes.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.Months.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.Negate() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan() -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int days, int hours, int minutes, int seconds) -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int days, int hours, int minutes, int seconds, int milliseconds) -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int months, int days, int hours, int minutes, int seconds, int milliseconds) -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int months, int days, long ticks) -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(long ticks) -> void
-NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(System.TimeSpan timespan) -> void
-NpgsqlTypes.NpgsqlTimeSpan.Seconds.get -> int
-NpgsqlTypes.NpgsqlTimeSpan.Subtract(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.Ticks.get -> long
-NpgsqlTypes.NpgsqlTimeSpan.Time.get -> System.TimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.TotalDays.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalHours.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalMicroseconds.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalMilliseconds.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalMinutes.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalMonths.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalSeconds.get -> double
-NpgsqlTypes.NpgsqlTimeSpan.TotalTicks.get -> long
-NpgsqlTypes.NpgsqlTimeSpan.UnjustifyDays() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.UnjustifyInterval() -> NpgsqlTypes.NpgsqlTimeSpan
-NpgsqlTypes.NpgsqlTimeSpan.UnjustifyMonths() -> NpgsqlTypes.NpgsqlTimeSpan
NpgsqlTypes.NpgsqlTsQuery
NpgsqlTypes.NpgsqlTsQuery.Kind.get -> NpgsqlTypes.NpgsqlTsQuery.NodeKind
NpgsqlTypes.NpgsqlTsQuery.NodeKind
@@ -1593,8 +1516,17 @@ override Npgsql.NpgsqlCommand.CommandTimeout.get -> int
override Npgsql.NpgsqlCommand.CommandTimeout.set -> void
override Npgsql.NpgsqlCommand.CommandType.get -> System.Data.CommandType
override Npgsql.NpgsqlCommand.CommandType.set -> void
+override Npgsql.NpgsqlCommand.CreateDbParameter() -> System.Data.Common.DbParameter!
+override Npgsql.NpgsqlCommand.DbConnection.get -> System.Data.Common.DbConnection?
+override Npgsql.NpgsqlCommand.DbConnection.set -> void
+override Npgsql.NpgsqlCommand.DbParameterCollection.get -> System.Data.Common.DbParameterCollection!
+override Npgsql.NpgsqlCommand.DbTransaction.get -> System.Data.Common.DbTransaction?
+override Npgsql.NpgsqlCommand.DbTransaction.set -> void
override Npgsql.NpgsqlCommand.DesignTimeVisible.get -> bool
override Npgsql.NpgsqlCommand.DesignTimeVisible.set -> void
+override Npgsql.NpgsqlCommand.Dispose(bool disposing) -> void
+override Npgsql.NpgsqlCommand.ExecuteDbDataReader(System.Data.CommandBehavior behavior) -> System.Data.Common.DbDataReader!
+override Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
override Npgsql.NpgsqlCommand.ExecuteNonQuery() -> int
override Npgsql.NpgsqlCommand.ExecuteNonQueryAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
override Npgsql.NpgsqlCommand.ExecuteScalar() -> object?
@@ -1684,6 +1616,7 @@ override Npgsql.NpgsqlDataReader.ReadAsync(System.Threading.CancellationToken ca
override Npgsql.NpgsqlDataReader.RecordsAffected.get -> int
override Npgsql.NpgsqlDataReader.this[int ordinal].get -> object!
override Npgsql.NpgsqlDataReader.this[string! name].get -> object!
+override Npgsql.NpgsqlDataSource.ConnectionString.get -> string!
override Npgsql.NpgsqlException.DbBatchCommand.get -> System.Data.Common.DbBatchCommand?
override Npgsql.NpgsqlException.IsTransient.get -> bool
override Npgsql.NpgsqlFactory.CanCreateBatch.get -> bool
@@ -1696,6 +1629,7 @@ override Npgsql.NpgsqlFactory.CreateCommandBuilder() -> System.Data.Common.DbCom
override Npgsql.NpgsqlFactory.CreateConnection() -> System.Data.Common.DbConnection!
override Npgsql.NpgsqlFactory.CreateConnectionStringBuilder() -> System.Data.Common.DbConnectionStringBuilder!
override Npgsql.NpgsqlFactory.CreateDataAdapter() -> System.Data.Common.DbDataAdapter!
+override Npgsql.NpgsqlFactory.CreateDataSource(string! connectionString) -> System.Data.Common.DbDataSource!
override Npgsql.NpgsqlFactory.CreateParameter() -> System.Data.Common.DbParameter!
override Npgsql.NpgsqlLargeObjectStream.CanRead.get -> bool
override Npgsql.NpgsqlLargeObjectStream.CanSeek.get -> bool
@@ -1807,6 +1741,7 @@ override Npgsql.NpgsqlTransaction.RollbackAsync(string! name, System.Threading.C
override Npgsql.NpgsqlTransaction.RollbackAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
override Npgsql.NpgsqlTransaction.Save(string! name) -> void
override Npgsql.NpgsqlTransaction.SaveAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+override Npgsql.NpgsqlTransaction.SupportsSavepoints.get -> bool
override Npgsql.PostgresException.GetObjectData(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
override Npgsql.PostgresException.IsTransient.get -> bool
override Npgsql.PostgresException.SqlState.get -> string!
@@ -1829,12 +1764,6 @@ override NpgsqlTypes.NpgsqlBox.ToString() -> string!
override NpgsqlTypes.NpgsqlCircle.Equals(object? obj) -> bool
override NpgsqlTypes.NpgsqlCircle.GetHashCode() -> int
override NpgsqlTypes.NpgsqlCircle.ToString() -> string!
-override NpgsqlTypes.NpgsqlDate.Equals(object? obj) -> bool
-override NpgsqlTypes.NpgsqlDate.GetHashCode() -> int
-override NpgsqlTypes.NpgsqlDate.ToString() -> string!
-override NpgsqlTypes.NpgsqlDateTime.Equals(object? obj) -> bool
-override NpgsqlTypes.NpgsqlDateTime.GetHashCode() -> int
-override NpgsqlTypes.NpgsqlDateTime.ToString() -> string!
override NpgsqlTypes.NpgsqlInet.Equals(object? obj) -> bool
override NpgsqlTypes.NpgsqlInet.GetHashCode() -> int
override NpgsqlTypes.NpgsqlInet.ToString() -> string!
@@ -1868,9 +1797,6 @@ override NpgsqlTypes.NpgsqlRange.ToString() -> string!
override NpgsqlTypes.NpgsqlTid.Equals(object? o) -> bool
override NpgsqlTypes.NpgsqlTid.GetHashCode() -> int
override NpgsqlTypes.NpgsqlTid.ToString() -> string!
-override NpgsqlTypes.NpgsqlTimeSpan.Equals(object? obj) -> bool
-override NpgsqlTypes.NpgsqlTimeSpan.GetHashCode() -> int
-override NpgsqlTypes.NpgsqlTimeSpan.ToString() -> string!
override NpgsqlTypes.NpgsqlTsQuery.Equals(object? obj) -> bool
override NpgsqlTypes.NpgsqlTsQuery.GetHashCode() -> int
override NpgsqlTypes.NpgsqlTsQuery.ToString() -> string!
@@ -1911,24 +1837,19 @@ override sealed Npgsql.NpgsqlParameter.SourceColumnNullMapping.get -> bool
override sealed Npgsql.NpgsqlParameter.SourceColumnNullMapping.set -> void
override sealed Npgsql.NpgsqlParameter.SourceVersion.get -> System.Data.DataRowVersion
override sealed Npgsql.NpgsqlParameter.SourceVersion.set -> void
-static Npgsql.Logging.NpgsqlLogManager.IsParameterLoggingEnabled.get -> bool
-static Npgsql.Logging.NpgsqlLogManager.IsParameterLoggingEnabled.set -> void
-static Npgsql.Logging.NpgsqlLogManager.Provider.get -> Npgsql.Logging.INpgsqlLoggingProvider!
-static Npgsql.Logging.NpgsqlLogManager.Provider.set -> void
static Npgsql.NameTranslation.NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(string! name) -> string!
static Npgsql.NpgsqlCommandBuilder.DeriveParameters(Npgsql.NpgsqlCommand! command) -> void
static Npgsql.NpgsqlConnection.ClearAllPools() -> void
static Npgsql.NpgsqlConnection.ClearPool(Npgsql.NpgsqlConnection! connection) -> void
static Npgsql.NpgsqlConnection.GlobalTypeMapper.get -> Npgsql.TypeMapping.INpgsqlTypeMapper!
-static Npgsql.NpgsqlConnection.MapCompositeGlobally(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-static Npgsql.NpgsqlConnection.MapEnumGlobally(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-static Npgsql.NpgsqlConnection.UnmapCompositeGlobally(string! pgName, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-static Npgsql.NpgsqlConnection.UnmapEnumGlobally(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-static Npgsql.Replication.Internal.LogicalReplicationConnectionExtensions.CreateLogicalReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, string! outputPlugin, bool isTemporary = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+static Npgsql.NpgsqlDataSource.Create(Npgsql.NpgsqlConnectionStringBuilder! connectionStringBuilder) -> Npgsql.NpgsqlDataSource!
+static Npgsql.NpgsqlDataSource.Create(string! connectionString) -> Npgsql.NpgsqlDataSource!
+static Npgsql.NpgsqlLoggingConfiguration.InitializeLogging(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, bool parameterLoggingEnabled = false) -> void
+static Npgsql.Replication.Internal.LogicalReplicationConnectionExtensions.CreateLogicalReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, string! outputPlugin, bool isTemporary = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, bool twoPhase = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static Npgsql.Replication.Internal.LogicalReplicationConnectionExtensions.StartLogicalReplication(this Npgsql.Replication.LogicalReplicationConnection! connection, Npgsql.Replication.Internal.LogicalReplicationSlot! slot, System.Threading.CancellationToken cancellationToken, NpgsqlTypes.NpgsqlLogSequenceNumber? walLocation = null, System.Collections.Generic.IEnumerable>? options = null, bool bypassingStream = false) -> System.Collections.Generic.IAsyncEnumerable!
-static Npgsql.Replication.PgOutputConnectionExtensions.CreatePgOutputReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+static Npgsql.Replication.PgOutputConnectionExtensions.CreatePgOutputReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, bool twoPhase = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static Npgsql.Replication.PgOutputConnectionExtensions.StartReplication(this Npgsql.Replication.LogicalReplicationConnection! connection, Npgsql.Replication.PgOutput.PgOutputReplicationSlot! slot, Npgsql.Replication.PgOutput.PgOutputReplicationOptions! options, System.Threading.CancellationToken cancellationToken, NpgsqlTypes.NpgsqlLogSequenceNumber? walLocation = null) -> System.Collections.Generic.IAsyncEnumerable!
-static Npgsql.Replication.TestDecodingConnectionExtensions.CreateTestDecodingReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+static Npgsql.Replication.TestDecodingConnectionExtensions.CreateTestDecodingReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, bool twoPhase = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
static Npgsql.Replication.TestDecodingConnectionExtensions.StartReplication(this Npgsql.Replication.LogicalReplicationConnection! connection, Npgsql.Replication.TestDecoding.TestDecodingReplicationSlot! slot, System.Threading.CancellationToken cancellationToken, Npgsql.Replication.TestDecoding.TestDecodingOptions? options = null, NpgsqlTypes.NpgsqlLogSequenceNumber? walLocation = null) -> System.Collections.Generic.IAsyncEnumerable!
static NpgsqlTypes.NpgsqlBox.operator !=(NpgsqlTypes.NpgsqlBox x, NpgsqlTypes.NpgsqlBox y) -> bool
static NpgsqlTypes.NpgsqlBox.operator ==(NpgsqlTypes.NpgsqlBox x, NpgsqlTypes.NpgsqlBox y) -> bool
@@ -1936,45 +1857,6 @@ static NpgsqlTypes.NpgsqlBox.Parse(string! s) -> NpgsqlTypes.NpgsqlBox
static NpgsqlTypes.NpgsqlCircle.operator !=(NpgsqlTypes.NpgsqlCircle x, NpgsqlTypes.NpgsqlCircle y) -> bool
static NpgsqlTypes.NpgsqlCircle.operator ==(NpgsqlTypes.NpgsqlCircle x, NpgsqlTypes.NpgsqlCircle y) -> bool
static NpgsqlTypes.NpgsqlCircle.Parse(string! s) -> NpgsqlTypes.NpgsqlCircle
-static NpgsqlTypes.NpgsqlDate.explicit operator NpgsqlTypes.NpgsqlDate(System.DateOnly date) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.explicit operator NpgsqlTypes.NpgsqlDate(System.DateTime date) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.explicit operator System.DateOnly(NpgsqlTypes.NpgsqlDate date) -> System.DateOnly
-static NpgsqlTypes.NpgsqlDate.explicit operator System.DateTime(NpgsqlTypes.NpgsqlDate date) -> System.DateTime
-static NpgsqlTypes.NpgsqlDate.Now.get -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.operator !=(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-static NpgsqlTypes.NpgsqlDate.operator +(NpgsqlTypes.NpgsqlDate date, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.operator +(NpgsqlTypes.NpgsqlTimeSpan interval, NpgsqlTypes.NpgsqlDate date) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.operator -(NpgsqlTypes.NpgsqlDate date, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.operator -(NpgsqlTypes.NpgsqlDate dateX, NpgsqlTypes.NpgsqlDate dateY) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlDate.operator <(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-static NpgsqlTypes.NpgsqlDate.operator <=(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-static NpgsqlTypes.NpgsqlDate.operator ==(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-static NpgsqlTypes.NpgsqlDate.operator >(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-static NpgsqlTypes.NpgsqlDate.operator >=(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-static NpgsqlTypes.NpgsqlDate.Parse(string! str) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.ToDateOnly(NpgsqlTypes.NpgsqlDate date) -> System.DateOnly
-static NpgsqlTypes.NpgsqlDate.ToDateTime(NpgsqlTypes.NpgsqlDate date) -> System.DateTime
-static NpgsqlTypes.NpgsqlDate.Today.get -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.Tomorrow.get -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.ToNpgsqlDate(System.DateOnly date) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.ToNpgsqlDate(System.DateTime date) -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDate.TryParse(string! str, out NpgsqlTypes.NpgsqlDate date) -> bool
-static NpgsqlTypes.NpgsqlDate.Yesterday.get -> NpgsqlTypes.NpgsqlDate
-static NpgsqlTypes.NpgsqlDateTime.explicit operator System.DateTime(NpgsqlTypes.NpgsqlDateTime npgsqlDateTime) -> System.DateTime
-static NpgsqlTypes.NpgsqlDateTime.implicit operator NpgsqlTypes.NpgsqlDateTime(System.DateTime dateTime) -> NpgsqlTypes.NpgsqlDateTime
-static NpgsqlTypes.NpgsqlDateTime.Now.get -> NpgsqlTypes.NpgsqlDateTime
-static NpgsqlTypes.NpgsqlDateTime.operator !=(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-static NpgsqlTypes.NpgsqlDateTime.operator +(NpgsqlTypes.NpgsqlDateTime timestamp, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDateTime
-static NpgsqlTypes.NpgsqlDateTime.operator +(NpgsqlTypes.NpgsqlTimeSpan interval, NpgsqlTypes.NpgsqlDateTime timestamp) -> NpgsqlTypes.NpgsqlDateTime
-static NpgsqlTypes.NpgsqlDateTime.operator -(NpgsqlTypes.NpgsqlDateTime timestamp, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDateTime
-static NpgsqlTypes.NpgsqlDateTime.operator -(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlDateTime.operator <(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-static NpgsqlTypes.NpgsqlDateTime.operator <=(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-static NpgsqlTypes.NpgsqlDateTime.operator ==(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-static NpgsqlTypes.NpgsqlDateTime.operator >(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-static NpgsqlTypes.NpgsqlDateTime.operator >=(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-static NpgsqlTypes.NpgsqlDateTime.Parse(string! str) -> NpgsqlTypes.NpgsqlDateTime
-static NpgsqlTypes.NpgsqlDateTime.ToNpgsqlDateTime(System.DateTime dateTime) -> NpgsqlTypes.NpgsqlDateTime
static NpgsqlTypes.NpgsqlInet.explicit operator System.Net.IPAddress!(NpgsqlTypes.NpgsqlInet inet) -> System.Net.IPAddress!
static NpgsqlTypes.NpgsqlInet.implicit operator NpgsqlTypes.NpgsqlInet(System.Net.IPAddress! ip) -> NpgsqlTypes.NpgsqlInet
static NpgsqlTypes.NpgsqlInet.operator !=(NpgsqlTypes.NpgsqlInet x, NpgsqlTypes.NpgsqlInet y) -> bool
@@ -2019,32 +1901,6 @@ static NpgsqlTypes.NpgsqlRange.Parse(string! value) -> NpgsqlTypes.NpgsqlRang
static NpgsqlTypes.NpgsqlRange.RangeTypeConverter.Register() -> void
static NpgsqlTypes.NpgsqlTid.operator !=(NpgsqlTypes.NpgsqlTid left, NpgsqlTypes.NpgsqlTid right) -> bool
static NpgsqlTypes.NpgsqlTid.operator ==(NpgsqlTypes.NpgsqlTid left, NpgsqlTypes.NpgsqlTid right) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.Compare(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> int
-static NpgsqlTypes.NpgsqlTimeSpan.explicit operator System.TimeSpan(NpgsqlTypes.NpgsqlTimeSpan interval) -> System.TimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromDays(double days) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromHours(double hours) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromMicroseconds(double micro) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromMilliseconds(double milli) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromMinutes(double minutes) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromMonths(double months) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromSeconds(double seconds) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.FromTicks(long ticks) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.implicit operator NpgsqlTypes.NpgsqlTimeSpan(System.TimeSpan timespan) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.operator !=(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.operator +(NpgsqlTypes.NpgsqlTimeSpan x) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.operator +(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.operator -(NpgsqlTypes.NpgsqlTimeSpan x) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.operator -(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.operator <(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.operator <=(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.operator ==(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.operator >(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.operator >=(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-static NpgsqlTypes.NpgsqlTimeSpan.Parse(string! str) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.Plus(in NpgsqlTypes.NpgsqlTimeSpan x) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.ToNpgsqlTimeSpan(System.TimeSpan timespan) -> NpgsqlTypes.NpgsqlTimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.ToTimeSpan(in NpgsqlTypes.NpgsqlTimeSpan interval) -> System.TimeSpan
-static NpgsqlTypes.NpgsqlTimeSpan.TryParse(string! str, out NpgsqlTypes.NpgsqlTimeSpan result) -> bool
static NpgsqlTypes.NpgsqlTsQuery.operator !=(NpgsqlTypes.NpgsqlTsQuery? left, NpgsqlTypes.NpgsqlTsQuery? right) -> bool
static NpgsqlTypes.NpgsqlTsQuery.operator ==(NpgsqlTypes.NpgsqlTsQuery? left, NpgsqlTypes.NpgsqlTsQuery? right) -> bool
static NpgsqlTypes.NpgsqlTsQuery.Parse(string! value) -> NpgsqlTypes.NpgsqlTsQuery!
@@ -2054,19 +1910,7 @@ static NpgsqlTypes.NpgsqlTsVector.Lexeme.WordEntryPos.operator !=(NpgsqlTypes.Np
static NpgsqlTypes.NpgsqlTsVector.Lexeme.WordEntryPos.operator ==(NpgsqlTypes.NpgsqlTsVector.Lexeme.WordEntryPos left, NpgsqlTypes.NpgsqlTsVector.Lexeme.WordEntryPos right) -> bool
static NpgsqlTypes.NpgsqlTsVector.Parse(string! value) -> NpgsqlTypes.NpgsqlTsVector!
static readonly Npgsql.NpgsqlFactory.Instance -> Npgsql.NpgsqlFactory!
-static readonly NpgsqlTypes.NpgsqlDate.Epoch -> NpgsqlTypes.NpgsqlDate
-static readonly NpgsqlTypes.NpgsqlDate.Era -> NpgsqlTypes.NpgsqlDate
-static readonly NpgsqlTypes.NpgsqlDate.Infinity -> NpgsqlTypes.NpgsqlDate
-static readonly NpgsqlTypes.NpgsqlDate.MaxCalculableValue -> NpgsqlTypes.NpgsqlDate
-static readonly NpgsqlTypes.NpgsqlDate.MinCalculableValue -> NpgsqlTypes.NpgsqlDate
-static readonly NpgsqlTypes.NpgsqlDate.NegativeInfinity -> NpgsqlTypes.NpgsqlDate
-static readonly NpgsqlTypes.NpgsqlDateTime.Epoch -> NpgsqlTypes.NpgsqlDateTime
-static readonly NpgsqlTypes.NpgsqlDateTime.Era -> NpgsqlTypes.NpgsqlDateTime
-static readonly NpgsqlTypes.NpgsqlDateTime.Infinity -> NpgsqlTypes.NpgsqlDateTime
-static readonly NpgsqlTypes.NpgsqlDateTime.NegativeInfinity -> NpgsqlTypes.NpgsqlDateTime
static readonly NpgsqlTypes.NpgsqlLogSequenceNumber.Invalid -> NpgsqlTypes.NpgsqlLogSequenceNumber
static readonly NpgsqlTypes.NpgsqlRange.Empty -> NpgsqlTypes.NpgsqlRange
-static readonly NpgsqlTypes.NpgsqlTimeSpan.MaxValue -> NpgsqlTypes.NpgsqlTimeSpan
-static readonly NpgsqlTypes.NpgsqlTimeSpan.MinValue -> NpgsqlTypes.NpgsqlTimeSpan
-static readonly NpgsqlTypes.NpgsqlTimeSpan.Zero -> NpgsqlTypes.NpgsqlTimeSpan
-virtual Npgsql.Replication.PgOutput.ReplicationTuple.GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IAsyncEnumerator!
\ No newline at end of file
+virtual Npgsql.NpgsqlCommand.Clone() -> Npgsql.NpgsqlCommand!
+virtual Npgsql.Replication.PgOutput.ReplicationTuple.GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IAsyncEnumerator!
diff --git a/src/Npgsql/PublicAPI.Unshipped.txt b/src/Npgsql/PublicAPI.Unshipped.txt
index b032a873c1..38c7b7978c 100644
--- a/src/Npgsql/PublicAPI.Unshipped.txt
+++ b/src/Npgsql/PublicAPI.Unshipped.txt
@@ -1,367 +1,2 @@
#nullable enable
-Npgsql.NpgsqlBatch.EnableErrorBarriers.get -> bool
-Npgsql.NpgsqlBatch.EnableErrorBarriers.set -> void
-Npgsql.NpgsqlBatchCommand.AppendErrorBarrier.get -> bool?
-Npgsql.NpgsqlBatchCommand.AppendErrorBarrier.set -> void
-Npgsql.NpgsqlConnection.ReloadTypesAsync() -> System.Threading.Tasks.Task!
-Npgsql.NpgsqlDataSource
-Npgsql.NpgsqlDataSource.CreateBatch() -> Npgsql.NpgsqlBatch!
-Npgsql.NpgsqlDataSource.CreateCommand(string? commandText = null) -> Npgsql.NpgsqlCommand!
-Npgsql.NpgsqlDataSource.CreateConnection() -> Npgsql.NpgsqlConnection!
-Npgsql.NpgsqlDataSource.OpenConnection() -> Npgsql.NpgsqlConnection!
-Npgsql.NpgsqlDataSource.OpenConnectionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
-Npgsql.NpgsqlDataSource.Password.set -> void
-Npgsql.NpgsqlDataSourceBuilder
-Npgsql.NpgsqlDataSourceBuilder.AddTypeResolverFactory(Npgsql.Internal.TypeHandling.TypeHandlerResolverFactory! resolverFactory) -> void
-Npgsql.NpgsqlDataSourceBuilder.Build() -> Npgsql.NpgsqlDataSource!
-Npgsql.NpgsqlDataSourceBuilder.BuildMultiHost() -> Npgsql.NpgsqlMultiHostDataSource!
-Npgsql.NpgsqlDataSourceBuilder.ConnectionString.get -> string!
-Npgsql.NpgsqlDataSourceBuilder.ConnectionStringBuilder.get -> Npgsql.NpgsqlConnectionStringBuilder!
-Npgsql.NpgsqlDataSourceBuilder.DefaultNameTranslator.get -> Npgsql.INpgsqlNameTranslator!
-Npgsql.NpgsqlDataSourceBuilder.DefaultNameTranslator.set -> void
-Npgsql.NpgsqlDataSourceBuilder.EnableParameterLogging(bool parameterLoggingEnabled = true) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.MapComposite(System.Type! clrType, string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
-Npgsql.NpgsqlDataSourceBuilder.MapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
-Npgsql.NpgsqlDataSourceBuilder.MapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> Npgsql.TypeMapping.INpgsqlTypeMapper!
-Npgsql.NpgsqlDataSourceBuilder.NpgsqlDataSourceBuilder(string? connectionString = null) -> void
-Npgsql.NpgsqlDataSourceBuilder.UnmapComposite(System.Type! clrType, string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> bool
-Npgsql.NpgsqlDataSourceBuilder.UnmapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> bool
-Npgsql.NpgsqlDataSourceBuilder.UnmapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> bool
-Npgsql.NpgsqlDataSourceBuilder.UseClientCertificate(System.Security.Cryptography.X509Certificates.X509Certificate? clientCertificate) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.UseClientCertificates(System.Security.Cryptography.X509Certificates.X509CertificateCollection? clientCertificates) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.UseClientCertificatesCallback(System.Action? clientCertificatesCallback) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.UseLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory? loggerFactory) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.UsePeriodicPasswordProvider(System.Func>? passwordProvider, System.TimeSpan successRefreshInterval, System.TimeSpan failureRefreshInterval) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.UsePhysicalConnectionInitializer(System.Action? connectionInitializer, System.Func? connectionInitializerAsync) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlDataSourceBuilder.UseUserCertificateValidationCallback(System.Net.Security.RemoteCertificateValidationCallback! userCertificateValidationCallback) -> Npgsql.NpgsqlDataSourceBuilder!
-Npgsql.NpgsqlLoggingConfiguration
-Npgsql.NpgsqlMultiHostDataSource
-Npgsql.NpgsqlMultiHostDataSource.ClearDatabaseStates() -> void
-Npgsql.NpgsqlMultiHostDataSource.CreateConnection(Npgsql.TargetSessionAttributes targetSessionAttributes) -> Npgsql.NpgsqlConnection!
-Npgsql.NpgsqlMultiHostDataSource.OpenConnection(Npgsql.TargetSessionAttributes targetSessionAttributes) -> Npgsql.NpgsqlConnection!
-Npgsql.NpgsqlMultiHostDataSource.OpenConnectionAsync(Npgsql.TargetSessionAttributes targetSessionAttributes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
-Npgsql.NpgsqlMultiHostDataSource.WithTargetSession(Npgsql.TargetSessionAttributes targetSessionAttributes) -> Npgsql.NpgsqlDataSource!
-Npgsql.Replication.PgOutput.Messages.BeginPrepareMessage
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.CommitPreparedFlags
-Npgsql.Replication.PgOutput.Messages.CommitPreparedMessage.TransactionCommitTimestamp.get -> System.DateTime
-Npgsql.Replication.PgOutput.Messages.PreparedTransactionControlMessage
-Npgsql.Replication.PgOutput.Messages.PreparedTransactionControlMessage.TransactionGid.get -> string!
-Npgsql.Replication.PgOutput.Messages.PrepareMessage
-Npgsql.Replication.PgOutput.Messages.PrepareMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags
-Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags
-Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.PrepareMessage.PrepareFlags
-Npgsql.Replication.PgOutput.Messages.PrepareMessageBase
-Npgsql.Replication.PgOutput.Messages.PrepareMessageBase.PrepareEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
-Npgsql.Replication.PgOutput.Messages.PrepareMessageBase.PrepareLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
-Npgsql.Replication.PgOutput.Messages.PrepareMessageBase.TransactionPrepareTimestamp.get -> System.DateTime
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.PreparedTransactionEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedEndLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.RollbackPreparedFlags
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.TransactionPrepareTimestamp.get -> System.DateTime
-Npgsql.Replication.PgOutput.Messages.RollbackPreparedMessage.TransactionRollbackTimestamp.get -> System.DateTime
-Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage
-Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.Flags.get -> Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags
-Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags
-Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags.None = 0 -> Npgsql.Replication.PgOutput.Messages.StreamPrepareMessage.StreamPrepareFlags
-Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(string! publicationName, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null, bool? twoPhase = null) -> void
-Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(System.Collections.Generic.IEnumerable! publicationNames, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null, bool? twoPhase = null) -> void
-Npgsql.Replication.PgOutput.PgOutputReplicationOptions.TwoPhase.get -> bool?
-Npgsql.Replication.PhysicalReplicationConnection.ReadReplicationSlot(string! slotName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
-Npgsql.Replication.PhysicalReplicationConnection.StartReplication(Npgsql.Replication.PhysicalReplicationSlot! slot, System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerable!
-Npgsql.Replication.PhysicalReplicationConnection.StartReplication(Npgsql.Replication.PhysicalReplicationSlot? slot, NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, ulong timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
-Npgsql.Replication.PhysicalReplicationConnection.StartReplication(NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, ulong timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
-Npgsql.Replication.PhysicalReplicationSlot.PhysicalReplicationSlot(string! slotName, NpgsqlTypes.NpgsqlLogSequenceNumber? restartLsn = null, ulong? restartTimeline = null) -> void
-Npgsql.Replication.PhysicalReplicationSlot.RestartLsn.get -> NpgsqlTypes.NpgsqlLogSequenceNumber?
-Npgsql.Replication.PhysicalReplicationSlot.RestartTimeline.get -> ulong?
-Npgsql.Schema.NpgsqlDbColumn.IsIdentity.get -> bool?
-Npgsql.Schema.NpgsqlDbColumn.IsIdentity.set -> void
-Npgsql.StatementType.Call = 11 -> Npgsql.StatementType
-Npgsql.TypeMapping.INpgsqlTypeMapper.DefaultNameTranslator.set -> void
-override Npgsql.NpgsqlCommand.CreateDbParameter() -> System.Data.Common.DbParameter!
-override Npgsql.NpgsqlCommand.DbConnection.get -> System.Data.Common.DbConnection?
-override Npgsql.NpgsqlCommand.DbConnection.set -> void
-override Npgsql.NpgsqlCommand.DbParameterCollection.get -> System.Data.Common.DbParameterCollection!
-override Npgsql.NpgsqlCommand.DbTransaction.get -> System.Data.Common.DbTransaction?
-override Npgsql.NpgsqlCommand.DbTransaction.set -> void
-override Npgsql.NpgsqlCommand.Dispose(bool disposing) -> void
-override Npgsql.NpgsqlCommand.ExecuteDbDataReader(System.Data.CommandBehavior behavior) -> System.Data.Common.DbDataReader!
-override Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
-override Npgsql.NpgsqlDataSource.ConnectionString.get -> string!
-override Npgsql.NpgsqlFactory.CreateDataSource(string! connectionString) -> System.Data.Common.DbDataSource!
-static Npgsql.NpgsqlDataSource.Create(Npgsql.NpgsqlConnectionStringBuilder! connectionStringBuilder) -> Npgsql.NpgsqlDataSource!
-static Npgsql.NpgsqlDataSource.Create(string! connectionString) -> Npgsql.NpgsqlDataSource!
-static Npgsql.NpgsqlLoggingConfiguration.InitializeLogging(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, bool parameterLoggingEnabled = false) -> void
-static Npgsql.Replication.Internal.LogicalReplicationConnectionExtensions.CreateLogicalReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, string! outputPlugin, bool isTemporary = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, bool twoPhase = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
-static Npgsql.Replication.PgOutputConnectionExtensions.CreatePgOutputReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, bool twoPhase = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
-static Npgsql.Replication.TestDecodingConnectionExtensions.CreateTestDecodingReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, bool twoPhase = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
-virtual Npgsql.NpgsqlCommand.Clone() -> Npgsql.NpgsqlCommand!
-
-*REMOVED*Npgsql.NpgsqlConnection.Settings.get -> Npgsql.NpgsqlConnectionStringBuilder!
-*REMOVED*abstract Npgsql.Logging.NpgsqlLogger.IsEnabled(Npgsql.Logging.NpgsqlLogLevel level) -> bool
-*REMOVED*abstract Npgsql.Logging.NpgsqlLogger.Log(Npgsql.Logging.NpgsqlLogLevel level, int connectorId, string! msg, System.Exception? exception = null) -> void
-*REMOVED*const NpgsqlTypes.NpgsqlDate.MaxYear = 5874897 -> int
-*REMOVED*const NpgsqlTypes.NpgsqlDate.MinYear = -4714 -> int
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.DaysPerMonth = 30 -> int
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.HoursPerDay = 24 -> int
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.MonthsPerYear = 12 -> int
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerDay = 864000000000 -> long
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerHour = 36000000000 -> long
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMicrosecond = 10 -> long
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMillsecond = 10000 -> long
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMinute = 600000000 -> long
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerMonth = 25920000000000 -> long
-*REMOVED*const NpgsqlTypes.NpgsqlTimeSpan.TicksPerSecond = 10000000 -> long
-*REMOVED*Npgsql.NpgsqlConnection.MapComposite(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-*REMOVED*Npgsql.NpgsqlConnection.MapEnum(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-*REMOVED*Npgsql.NpgsqlConnection.PhysicalOpenAsyncCallback.get -> Npgsql.PhysicalOpenAsyncCallback?
-*REMOVED*Npgsql.NpgsqlConnection.PhysicalOpenAsyncCallback.set -> void
-*REMOVED*Npgsql.NpgsqlConnection.PhysicalOpenCallback.get -> Npgsql.PhysicalOpenCallback?
-*REMOVED*Npgsql.NpgsqlConnection.PhysicalOpenCallback.set -> void
-*REMOVED*Npgsql.NpgsqlConnectionStringPropertyAttribute
-*REMOVED*Npgsql.NpgsqlConnectionStringPropertyAttribute.NpgsqlConnectionStringPropertyAttribute() -> void
-*REMOVED*Npgsql.NpgsqlConnectionStringPropertyAttribute.NpgsqlConnectionStringPropertyAttribute(params string![]! synonyms) -> void
-*REMOVED*Npgsql.NpgsqlConnectionStringPropertyAttribute.Synonyms.get -> string![]!
-*REMOVED*Npgsql.Logging.ConsoleLoggingProvider
-*REMOVED*Npgsql.Logging.ConsoleLoggingProvider.ConsoleLoggingProvider(Npgsql.Logging.NpgsqlLogLevel minLevel = Npgsql.Logging.NpgsqlLogLevel.Info, bool printLevel = false, bool printConnectorId = false) -> void
-*REMOVED*Npgsql.Logging.ConsoleLoggingProvider.CreateLogger(string! name) -> Npgsql.Logging.NpgsqlLogger!
-*REMOVED*Npgsql.Logging.INpgsqlLoggingProvider
-*REMOVED*Npgsql.Logging.INpgsqlLoggingProvider.CreateLogger(string! name) -> Npgsql.Logging.NpgsqlLogger!
-*REMOVED*Npgsql.Logging.NpgsqlLogger
-*REMOVED*Npgsql.Logging.NpgsqlLogger.NpgsqlLogger() -> void
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel.Debug = 2 -> Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel.Error = 5 -> Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel.Fatal = 6 -> Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel.Info = 3 -> Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel.Trace = 1 -> Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogLevel.Warn = 4 -> Npgsql.Logging.NpgsqlLogLevel
-*REMOVED*Npgsql.Logging.NpgsqlLogManager
-*REMOVED*Npgsql.NpgsqlCommand.Clone() -> Npgsql.NpgsqlCommand!
-*REMOVED*Npgsql.NpgsqlDataReader.GetDate(int ordinal) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*Npgsql.NpgsqlDataReader.GetInterval(int ordinal) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*Npgsql.NpgsqlDataReader.GetTimeStamp(int ordinal) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*Npgsql.PhysicalOpenAsyncCallback
-*REMOVED*Npgsql.PhysicalOpenCallback
-*REMOVED*Npgsql.Replication.PhysicalReplicationSlot.PhysicalReplicationSlot(string! slotName) -> void
-*REMOVED*Npgsql.Replication.PhysicalReplicationConnection.StartReplication(Npgsql.Replication.PhysicalReplicationSlot? slot, NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, uint timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
-*REMOVED*Npgsql.Replication.PhysicalReplicationConnection.StartReplication(NpgsqlTypes.NpgsqlLogSequenceNumber walLocation, System.Threading.CancellationToken cancellationToken, uint timeline = 0) -> System.Collections.Generic.IAsyncEnumerable!
-*REMOVED*Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(string! publicationName, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null) -> void
-*REMOVED*Npgsql.Replication.PgOutput.PgOutputReplicationOptions.PgOutputReplicationOptions(System.Collections.Generic.IEnumerable! publicationNames, ulong protocolVersion, bool? binary = null, bool? streaming = null, bool? messages = null) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDate.Add(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDate.AddDays(int days) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDate.AddMonths(int months) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDate.AddYears(int years) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDate.Compare(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.Compare(object? x, object? y) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.CompareTo(NpgsqlTypes.NpgsqlDate other) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.CompareTo(object? o) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.Day.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.DayOfWeek.get -> System.DayOfWeek
-*REMOVED*NpgsqlTypes.NpgsqlDate.DayOfYear.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.Equals(NpgsqlTypes.NpgsqlDate other) -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDate.IsFinite.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDate.IsInfinity.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDate.IsLeapYear.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDate.IsNegativeInfinity.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDate.Month.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDate.NpgsqlDate() -> void
-*REMOVED*NpgsqlTypes.NpgsqlDate.NpgsqlDate(int year, int month, int day) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDate.NpgsqlDate(NpgsqlTypes.NpgsqlDate copyFrom) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDate.NpgsqlDate(System.DateOnly date) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDate.NpgsqlDate(System.DateTime dateTime) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDate.Subtract(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDate.Year.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Add(in NpgsqlTypes.NpgsqlTimeSpan value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Add(System.TimeSpan value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddDays(double value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddHours(double value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddMilliseconds(double value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddMinutes(double value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddMonths(int value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddSeconds(double value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddTicks(long value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.AddYears(int value) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Compare(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Compare(object? x, object? y) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.CompareTo(NpgsqlTypes.NpgsqlDateTime other) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.CompareTo(object? o) -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Date.get -> NpgsqlTypes.NpgsqlDate
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Day.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.DayOfWeek.get -> System.DayOfWeek
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.DayOfYear.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Equals(NpgsqlTypes.NpgsqlDateTime other) -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Hour.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.IsFinite.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.IsInfinity.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.IsLeapYear.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.IsNegativeInfinity.get -> bool
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Kind.get -> System.DateTimeKind
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Millisecond.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Minute.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Month.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Normalize() -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime() -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(int year, int month, int day, int hours, int minutes, int seconds, int milliseconds, System.DateTimeKind kind = System.DateTimeKind.Unspecified) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(int year, int month, int day, int hours, int minutes, int seconds, System.DateTimeKind kind = System.DateTimeKind.Unspecified) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(long ticks) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(long ticks, System.DateTimeKind kind) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(NpgsqlTypes.NpgsqlDate date) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(NpgsqlTypes.NpgsqlDate date, System.TimeSpan time, System.DateTimeKind kind = System.DateTimeKind.Unspecified) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.NpgsqlDateTime(System.DateTime dateTime) -> void
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Second.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Subtract(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Subtract(NpgsqlTypes.NpgsqlDateTime timestamp) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Ticks.get -> long
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Time.get -> System.TimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.ToDateTime() -> System.DateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.ToLocalTime() -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.ToUniversalTime() -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*NpgsqlTypes.NpgsqlDateTime.Year.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Add(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Canonicalize() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.CompareTo(NpgsqlTypes.NpgsqlTimeSpan other) -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.CompareTo(object? other) -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Days.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Duration() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Equals(NpgsqlTypes.NpgsqlTimeSpan other) -> bool
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Hours.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.JustifyDays() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.JustifyInterval() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.JustifyMonths() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Microseconds.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Milliseconds.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Minutes.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Months.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Negate() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan() -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int days, int hours, int minutes, int seconds) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int days, int hours, int minutes, int seconds, int milliseconds) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int months, int days, int hours, int minutes, int seconds, int milliseconds) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int months, int days, long ticks) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(long ticks) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.NpgsqlTimeSpan(System.TimeSpan timespan) -> void
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Seconds.get -> int
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Subtract(in NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Ticks.get -> long
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.Time.get -> System.TimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalDays.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalHours.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalMicroseconds.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalMilliseconds.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalMinutes.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalMonths.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalSeconds.get -> double
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.TotalTicks.get -> long
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.UnjustifyDays() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.UnjustifyInterval() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*NpgsqlTypes.NpgsqlTimeSpan.UnjustifyMonths() -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*override NpgsqlTypes.NpgsqlDate.Equals(object? obj) -> bool
-*REMOVED*override NpgsqlTypes.NpgsqlDate.GetHashCode() -> int
-*REMOVED*override NpgsqlTypes.NpgsqlDate.ToString() -> string!
-*REMOVED*override NpgsqlTypes.NpgsqlDateTime.Equals(object? obj) -> bool
-*REMOVED*override NpgsqlTypes.NpgsqlDateTime.GetHashCode() -> int
-*REMOVED*override NpgsqlTypes.NpgsqlDateTime.ToString() -> string!
-*REMOVED*override NpgsqlTypes.NpgsqlTimeSpan.Equals(object? obj) -> bool
-*REMOVED*override NpgsqlTypes.NpgsqlTimeSpan.GetHashCode() -> int
-*REMOVED*override NpgsqlTypes.NpgsqlTimeSpan.ToString() -> string!
-*REMOVED*static Npgsql.Logging.NpgsqlLogManager.IsParameterLoggingEnabled.get -> bool
-*REMOVED*static Npgsql.Logging.NpgsqlLogManager.IsParameterLoggingEnabled.set -> void
-*REMOVED*static Npgsql.Logging.NpgsqlLogManager.Provider.get -> Npgsql.Logging.INpgsqlLoggingProvider!
-*REMOVED*static Npgsql.Logging.NpgsqlLogManager.Provider.set -> void
-*REMOVED*static Npgsql.NpgsqlConnection.MapCompositeGlobally(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-*REMOVED*static Npgsql.NpgsqlConnection.MapEnumGlobally(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-*REMOVED*static Npgsql.NpgsqlConnection.UnmapCompositeGlobally(string! pgName, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-*REMOVED*static Npgsql.NpgsqlConnection.UnmapEnumGlobally(string? pgName = null, Npgsql.INpgsqlNameTranslator? nameTranslator = null) -> void
-*REMOVED*static Npgsql.Replication.PgOutputConnectionExtensions.CreatePgOutputReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
-*REMOVED*static Npgsql.Replication.TestDecodingConnectionExtensions.CreateTestDecodingReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, bool temporarySlot = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
-*REMOVED*static NpgsqlTypes.NpgsqlDate.explicit operator NpgsqlTypes.NpgsqlDate(System.DateOnly date) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.explicit operator NpgsqlTypes.NpgsqlDate(System.DateTime date) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.explicit operator System.DateOnly(NpgsqlTypes.NpgsqlDate date) -> System.DateOnly
-*REMOVED*static NpgsqlTypes.NpgsqlDate.explicit operator System.DateTime(NpgsqlTypes.NpgsqlDate date) -> System.DateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDate.Now.get -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator !=(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator +(NpgsqlTypes.NpgsqlDate date, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator +(NpgsqlTypes.NpgsqlTimeSpan interval, NpgsqlTypes.NpgsqlDate date) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator -(NpgsqlTypes.NpgsqlDate date, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator -(NpgsqlTypes.NpgsqlDate dateX, NpgsqlTypes.NpgsqlDate dateY) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator <(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator <=(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator ==(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator >(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.operator >=(NpgsqlTypes.NpgsqlDate x, NpgsqlTypes.NpgsqlDate y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.Parse(string! str) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.ToDateOnly(NpgsqlTypes.NpgsqlDate date) -> System.DateOnly
-*REMOVED*static NpgsqlTypes.NpgsqlDate.ToDateTime(NpgsqlTypes.NpgsqlDate date) -> System.DateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDate.Today.get -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.Tomorrow.get -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.ToNpgsqlDate(System.DateOnly date) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.ToNpgsqlDate(System.DateTime date) -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDate.TryParse(string! str, out NpgsqlTypes.NpgsqlDate date) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDate.Yesterday.get -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.explicit operator System.DateTime(NpgsqlTypes.NpgsqlDateTime npgsqlDateTime) -> System.DateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.implicit operator NpgsqlTypes.NpgsqlDateTime(System.DateTime dateTime) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.Now.get -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator !=(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator +(NpgsqlTypes.NpgsqlDateTime timestamp, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator +(NpgsqlTypes.NpgsqlTimeSpan interval, NpgsqlTypes.NpgsqlDateTime timestamp) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator -(NpgsqlTypes.NpgsqlDateTime timestamp, NpgsqlTypes.NpgsqlTimeSpan interval) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator -(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator <(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator <=(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator ==(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator >(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.operator >=(NpgsqlTypes.NpgsqlDateTime x, NpgsqlTypes.NpgsqlDateTime y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.Parse(string! str) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlDateTime.ToNpgsqlDateTime(System.DateTime dateTime) -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.Compare(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> int
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.explicit operator System.TimeSpan(NpgsqlTypes.NpgsqlTimeSpan interval) -> System.TimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromDays(double days) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromHours(double hours) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromMicroseconds(double micro) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromMilliseconds(double milli) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromMinutes(double minutes) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromMonths(double months) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromSeconds(double seconds) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.FromTicks(long ticks) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.implicit operator NpgsqlTypes.NpgsqlTimeSpan(System.TimeSpan timespan) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator !=(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator +(NpgsqlTypes.NpgsqlTimeSpan x) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator +(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator -(NpgsqlTypes.NpgsqlTimeSpan x) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator -(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator <(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator <=(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator ==(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator >(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.operator >=(NpgsqlTypes.NpgsqlTimeSpan x, NpgsqlTypes.NpgsqlTimeSpan y) -> bool
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.Parse(string! str) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.Plus(in NpgsqlTypes.NpgsqlTimeSpan x) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.ToNpgsqlTimeSpan(System.TimeSpan timespan) -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.ToTimeSpan(in NpgsqlTypes.NpgsqlTimeSpan interval) -> System.TimeSpan
-*REMOVED*static NpgsqlTypes.NpgsqlTimeSpan.TryParse(string! str, out NpgsqlTypes.NpgsqlTimeSpan result) -> bool
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDate.Epoch -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDate.Era -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDate.Infinity -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDate.MaxCalculableValue -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDate.MinCalculableValue -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDate.NegativeInfinity -> NpgsqlTypes.NpgsqlDate
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDateTime.Epoch -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDateTime.Era -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDateTime.Infinity -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static readonly NpgsqlTypes.NpgsqlDateTime.NegativeInfinity -> NpgsqlTypes.NpgsqlDateTime
-*REMOVED*static readonly NpgsqlTypes.NpgsqlTimeSpan.MaxValue -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static readonly NpgsqlTypes.NpgsqlTimeSpan.MinValue -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static readonly NpgsqlTypes.NpgsqlTimeSpan.Zero -> NpgsqlTypes.NpgsqlTimeSpan
-*REMOVED*static Npgsql.Replication.Internal.LogicalReplicationConnectionExtensions.CreateLogicalReplicationSlot(this Npgsql.Replication.LogicalReplicationConnection! connection, string! slotName, string! outputPlugin, bool isTemporary = false, Npgsql.Replication.LogicalSlotSnapshotInitMode? slotSnapshotInitMode = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+Npgsql.NpgsqlBatch.CreateBatchCommand() -> Npgsql.NpgsqlBatchCommand!
diff --git a/src/Npgsql/Replication/PgOutput/PgOutputAsyncEnumerable.cs b/src/Npgsql/Replication/PgOutput/PgOutputAsyncEnumerable.cs
index 76d983a6ee..93bb38a7a3 100644
--- a/src/Npgsql/Replication/PgOutput/PgOutputAsyncEnumerable.cs
+++ b/src/Npgsql/Replication/PgOutput/PgOutputAsyncEnumerable.cs
@@ -131,6 +131,7 @@ async IAsyncEnumerator StartReplicationInternal(Canc
data.Init(checked((int)length), false);
yield return _logicalDecodingMessage.Populate(xLogData.WalStart, xLogData.WalEnd, xLogData.ServerClock, transactionXid,
flags, messageLsn, prefix, data);
+ await data.DisposeAsync();
continue;
}
case BackendReplicationMessageCode.Commit:
diff --git a/src/Npgsql/Replication/ReplicationConnection.cs b/src/Npgsql/Replication/ReplicationConnection.cs
index 6a09c13811..fac363e0f6 100644
--- a/src/Npgsql/Replication/ReplicationConnection.cs
+++ b/src/Npgsql/Replication/ReplicationConnection.cs
@@ -416,7 +416,7 @@ internal async Task CreateReplicationSlot(string command
{
case "physical":
var restartLsn = (string?)result[1];
- var restartTli = (ulong?)result[2];
+ var restartTli = (ulong?)(uint?)result[2];
return new PhysicalReplicationSlot(
slotName.ToLowerInvariant(),
restartLsn == null ? null : NpgsqlLogSequenceNumber.Parse(restartLsn),
@@ -668,6 +668,7 @@ async Task SendFeedback(bool waitOnSemaphore = false, bool requestReply = false,
if (buf.WriteSpaceLeft < len)
await connector.Flush(async: true, cancellationToken);
+ buf.StartMessage(len);
buf.WriteByte(FrontendMessageCode.CopyData);
buf.WriteInt32(len - 1);
buf.WriteByte((byte)'r'); // TODO: enum/const?
@@ -824,22 +825,10 @@ async Task
- public void RestartTimeoutWithoutReset() => _cts.CancelAfter(Timeout);
+ public void RestartTimeoutWithoutReset()
+ {
+ lock (lockObject)
+ {
+ // if there was an attempt to cancel while the connector was breaking
+ // we do nothing and return the default token
+ // as we're going to fail while reading or writing anyway
+ if (!isDisposed)
+ _cts.CancelAfter(Timeout);
+ }
+ }
///
/// Reset the wrapper to contain a unstarted and uncancelled
@@ -83,10 +104,21 @@ public CancellationToken Start(CancellationToken cancellationToken = default)
public CancellationToken Reset(CancellationToken cancellationToken = default)
{
_registration.Dispose();
- _cts.CancelAfter(InfiniteTimeSpan);
- if (_cts.IsCancellationRequested)
+ lock (lockObject)
{
- lock (lockObject)
+ // if there was an attempt to cancel while the connector was breaking
+ // we do nothing and return
+ // as we're going to fail while reading or writing anyway
+ if (isDisposed)
+ {
+#if DEBUG
+ _isRunning = false;
+#endif
+ return CancellationToken.None;
+ }
+
+ _cts.CancelAfter(InfiniteTimeSpan);
+ if (_cts.IsCancellationRequested)
{
_cts.Dispose();
_cts = new CancellationTokenSource();
@@ -129,7 +161,13 @@ public void ResetCts()
public void Stop()
{
_registration.Dispose();
- _cts.CancelAfter(InfiniteTimeSpan);
+ lock (lockObject)
+ {
+ // if there was an attempt to cancel while the connector was breaking
+ // we do nothing
+ if (!isDisposed)
+ _cts.CancelAfter(InfiniteTimeSpan);
+ }
#if DEBUG
_isRunning = false;
#endif
diff --git a/src/Npgsql/VolatileResourceManager.cs b/src/Npgsql/VolatileResourceManager.cs
index 84c28868e3..b95caac58e 100644
--- a/src/Npgsql/VolatileResourceManager.cs
+++ b/src/Npgsql/VolatileResourceManager.cs
@@ -17,6 +17,7 @@ namespace Npgsql;
sealed class VolatileResourceManager : ISinglePhaseNotification
{
NpgsqlConnector _connector;
+ NpgsqlDataSource _dataSource;
Transaction _transaction;
readonly string _txId;
NpgsqlTransaction _localTx = null!;
@@ -31,6 +32,7 @@ sealed class VolatileResourceManager : ISinglePhaseNotification
internal VolatileResourceManager(NpgsqlConnection connection, Transaction transaction)
{
_connector = connection.Connector!;
+ _dataSource = connection.NpgsqlDataSource;
_transaction = transaction;
// _tx gets disposed by System.Transactions at some point, but we want to be able to log its local ID
_txId = transaction.TransactionInformation.LocalIdentifier;
@@ -121,7 +123,11 @@ public void Commit(Enlistment enlistment)
// if the user continues to use their connection after disposing the scope, and the MSDTC
// requests a commit at that exact time.
// To avoid this, we open a new connection for performing the 2nd phase.
- using var conn2 = (NpgsqlConnection)((ICloneable)_connector.Connection).Clone();
+ var settings = _connector.Connection.Settings.Clone();
+ // Set Enlist to false because we might be in TransactionScope and we can't prepare transaction while being in an open transaction
+ // see #5246
+ settings.Enlist = false;
+ using var conn2 = _connector.Connection.CloneWith(settings.ConnectionString);
conn2.Open();
var connector = conn2.Connector!;
@@ -271,8 +277,10 @@ void Dispose()
{
// We're here for connections which were closed before their TransactionScope completes.
// These need to be closed now.
- // We should return the connector to the pool only if we've successfully removed it from the pending list
- if (_connector.TryRemovePendingEnlistedConnector(_transaction))
+ // We should return the connector to the pool only if we've successfully removed it from the pending list.
+ // Note that we remove it from the NpgsqlDataSource bound to connection and not to connector
+ // because of NpgsqlMultiHostDataSource which has its own list to which connection adds connectors.
+ if (_dataSource.TryRemovePendingEnlistedConnector(_connector, _transaction))
_connector.Return();
}
@@ -301,4 +309,4 @@ static System.Data.IsolationLevel ConvertIsolationLevel(IsolationLevel isolation
IsolationLevel.Snapshot => System.Data.IsolationLevel.Snapshot,
_ => System.Data.IsolationLevel.Unspecified
};
-}
\ No newline at end of file
+}
diff --git a/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs b/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs
index 48ddfd265a..c4b0249dc4 100644
--- a/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs
+++ b/test/Npgsql.NodaTime.Tests/NodaTimeTests.cs
@@ -642,6 +642,31 @@ public async Task Bug3438()
}
}
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/5867")]
+ public async Task Normalize_period_on_write()
+ {
+ var value = Period.FromTicks(-3675048768766);
+ var expected = value.Normalize();
+ var expectedAfterRoundtripBuilder = expected.ToBuilder();
+ // Postgres doesn't support nanoseconds, trim them to microseconds
+ expectedAfterRoundtripBuilder.Nanoseconds -= expected.Nanoseconds % 1000;
+ var expectedAfterRoundtrip = expectedAfterRoundtripBuilder.Build();
+
+ await using var conn = await OpenConnectionAsync();
+ await using var cmd = new NpgsqlCommand("SELECT $1, $2", conn);
+ cmd.Parameters.AddWithValue(value);
+ cmd.Parameters.AddWithValue(expected);
+
+ await using var reader = await cmd.ExecuteReaderAsync();
+ await reader.ReadAsync();
+
+ var dbValue = reader.GetFieldValue(0);
+ var dbExpected = reader.GetFieldValue(1);
+
+ Assert.That(dbValue, Is.EqualTo(dbExpected));
+ Assert.That(dbValue, Is.EqualTo(expectedAfterRoundtrip));
+ }
+
#endregion Interval
#region Support
diff --git a/test/Npgsql.PluginTests/GeoJSONTests.cs b/test/Npgsql.PluginTests/GeoJSONTests.cs
index 2f44d0ec18..0630eebc8d 100644
--- a/test/Npgsql.PluginTests/GeoJSONTests.cs
+++ b/test/Npgsql.PluginTests/GeoJSONTests.cs
@@ -8,6 +8,7 @@
using GeoJSON.Net.Geometry;
using Newtonsoft.Json;
using Npgsql.Tests;
+using NpgsqlTypes;
using NUnit.Framework;
using static Npgsql.Tests.TestUtil;
@@ -285,6 +286,128 @@ public async Task Roundtrip_geometry_geography()
}
}
+ [Test, TestCaseSource(nameof(Tests))]
+ public async Task Import_geometry(TestData data)
+ {
+ await using var conn = await OpenConnectionAsync(options: GeoJSONOptions.BoundingBox);
+ var table = await CreateTempTable(conn, "field geometry");
+
+ await using (var writer = await conn.BeginBinaryImportAsync($"COPY {table} (field) FROM STDIN BINARY"))
+ {
+ await writer.StartRowAsync();
+ await writer.WriteAsync(data.Geometry, NpgsqlDbType.Geometry);
+
+ var rowsWritten = await writer.CompleteAsync();
+ Assert.That(rowsWritten, Is.EqualTo(1));
+ }
+
+ await using var cmd = conn.CreateCommand();
+ cmd.CommandText = $"SELECT field FROM {table}";
+ await using var reader = await cmd.ExecuteReaderAsync();
+ Assert.IsTrue(await reader.ReadAsync());
+ var actual = reader.GetValue(0);
+ Assert.That(actual, Is.EqualTo(data.Geometry));
+ }
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4827")]
+ public async Task Import_big_geometry()
+ {
+ await using var conn = await OpenConnectionAsync();
+ var table = await CreateTempTable(conn, "id text, field geometry");
+
+ var geometry = new MultiLineString(new[] {
+ new LineString(
+ Enumerable.Range(1, 507)
+ .Select(i => new Position(longitude: i, latitude: i))
+ .Append(new Position(longitude: 1d, latitude: 1d))),
+ new LineString(new[] {
+ new Position(longitude: 1d, latitude: 1d),
+ new Position(longitude: 1d, latitude: 2d),
+ new Position(longitude: 1d, latitude: 3d),
+ new Position(longitude: 1d, latitude: 1d),
+ })
+ });
+
+ await using (var writer = await conn.BeginBinaryImportAsync($"COPY {table} (id, field) FROM STDIN BINARY"))
+ {
+ await writer.StartRowAsync();
+ await writer.WriteAsync("a", NpgsqlDbType.Text);
+ await writer.WriteAsync(geometry, NpgsqlDbType.Geometry);
+
+ var rowsWritten = await writer.CompleteAsync();
+ Assert.That(rowsWritten, Is.EqualTo(1));
+ }
+
+ await using var cmd = conn.CreateCommand();
+ cmd.CommandText = $"SELECT field FROM {table}";
+ await using var reader = await cmd.ExecuteReaderAsync();
+ Assert.IsTrue(await reader.ReadAsync());
+ var actual = reader.GetValue(0);
+ Assert.That(actual, Is.EqualTo(geometry));
+ }
+
+ [Test, TestCaseSource(nameof(Tests))]
+ public async Task Export_geometry(TestData data)
+ {
+ await using var conn = await OpenConnectionAsync(options: GeoJSONOptions.BoundingBox);
+ var table = await CreateTempTable(conn, "field geometry");
+
+ await using (var writer = await conn.BeginBinaryImportAsync($"COPY {table} (field) FROM STDIN BINARY"))
+ {
+ await writer.StartRowAsync();
+ await writer.WriteAsync(data.Geometry, NpgsqlDbType.Geometry);
+
+ var rowsWritten = await writer.CompleteAsync();
+ Assert.That(rowsWritten, Is.EqualTo(1));
+ }
+
+ await using (var reader = await conn.BeginBinaryExportAsync($"COPY {table} (field) TO STDOUT BINARY"))
+ {
+ await reader.StartRowAsync();
+ var field = await reader.ReadAsync(NpgsqlDbType.Geometry);
+ Assert.That(field, Is.EqualTo(data.Geometry));
+ }
+ }
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4830")]
+ public async Task Export_big_geometry()
+ {
+ await using var conn = await OpenConnectionAsync();
+ var table = await CreateTempTable(conn, "id text, field geometry");
+
+ var geometry = new Polygon(new[] {
+ new LineString(
+ Enumerable.Range(1, 507)
+ .Select(i => new Position(longitude: i, latitude: i))
+ .Append(new Position(longitude: 1d, latitude: 1d))),
+ new LineString(new[] {
+ new Position(longitude: 1d, latitude: 1d),
+ new Position(longitude: 1d, latitude: 2d),
+ new Position(longitude: 1d, latitude: 3d),
+ new Position(longitude: 1d, latitude: 1d),
+ })
+ });
+
+ await using (var writer = await conn.BeginBinaryImportAsync($"COPY {table} (id, field) FROM STDIN BINARY"))
+ {
+ await writer.StartRowAsync();
+ await writer.WriteAsync("aaaa", NpgsqlDbType.Text);
+ await writer.WriteAsync(geometry, NpgsqlDbType.Geometry);
+
+ var rowsWritten = await writer.CompleteAsync();
+ Assert.That(rowsWritten, Is.EqualTo(1));
+ }
+
+ await using (var reader = await conn.BeginBinaryExportAsync($"COPY {table} (id, field) TO STDOUT BINARY"))
+ {
+ await reader.StartRowAsync();
+ var id = await reader.ReadAsync();
+ var field = await reader.ReadAsync(NpgsqlDbType.Geometry);
+ Assert.That(id, Is.EqualTo("aaaa"));
+ Assert.That(field, Is.EqualTo(geometry));
+ }
+ }
+
ValueTask OpenConnectionAsync(GeoJSONOptions options = GeoJSONOptions.None)
=> GetDataSource(options).OpenConnectionAsync();
diff --git a/test/Npgsql.Tests/AutoPrepareTests.cs b/test/Npgsql.Tests/AutoPrepareTests.cs
index c81affc542..2e30304229 100644
--- a/test/Npgsql.Tests/AutoPrepareTests.cs
+++ b/test/Npgsql.Tests/AutoPrepareTests.cs
@@ -177,6 +177,10 @@ public void Promote_auto_to_explicit()
// cmd1's statement is no longer valid (has been closed), make sure it still works (will run unprepared)
cmd2.ExecuteScalar();
+
+ // Trigger autoprepare on a different query to confirm we didn't leave replaced statement in a bad state
+ using var cmd3 = new NpgsqlCommand("SELECT 2", conn);
+ cmd3.ExecuteNonQuery(); cmd3.ExecuteNonQuery();
}
[Test]
@@ -542,7 +546,7 @@ public async Task Batch_statement_execution_error_cleanup()
Assert.That(await conn.ExecuteScalarAsync("SELECT 3"), Is.EqualTo(3));
}
- [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4404")]
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4404"), IssueLink("https://github.com/npgsql/npgsql/issues/5220")]
public async Task SchemaOnly()
{
var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
@@ -559,6 +563,9 @@ public async Task SchemaOnly()
{
await using var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SchemaOnly);
}
+
+ // Make sure there is no protocol desync due to #5220
+ await cmd.ExecuteScalarAsync();
}
[Test]
diff --git a/test/Npgsql.Tests/BatchTests.cs b/test/Npgsql.Tests/BatchTests.cs
index 342076714a..432a715514 100644
--- a/test/Npgsql.Tests/BatchTests.cs
+++ b/test/Npgsql.Tests/BatchTests.cs
@@ -243,6 +243,29 @@ public async Task StatementType_Call()
Assert.That(batch.BatchCommands[0].StatementType, Is.EqualTo(StatementType.Call));
}
+ [Test]
+ public async Task CommandType_StoredProcedure()
+ {
+ await using var conn = await OpenConnectionAsync();
+ MinimumPgVersion(conn, "11.0", "Stored procedures are supported starting with PG 11");
+
+ var sproc = await GetTempProcedureName(conn);
+ await conn.ExecuteNonQueryAsync($"CREATE PROCEDURE {sproc}() LANGUAGE sql AS ''");
+
+ await using var batch = new NpgsqlBatch(conn)
+ {
+ BatchCommands = { new($"{sproc}") {CommandType = CommandType.StoredProcedure} }
+ };
+
+ await using var reader = await batch.ExecuteReaderAsync(Behavior);
+
+ // Consume SELECT result set to parse the CommandComplete
+ await reader.CloseAsync();
+
+ Assert.That(batch.BatchCommands[0].StatementType, Is.EqualTo(StatementType.Call));
+ }
+
+
[Test]
public async Task StatementType_Merge()
{
@@ -731,6 +754,30 @@ public async Task ExecuteScalar_without_parameters()
Assert.That(await batch.ExecuteScalarAsync(), Is.EqualTo(1));
}
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4264")]
+ public async Task Batch_with_auto_prepare_reuse()
+ {
+ var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ MaxAutoPrepare = 20
+ };
+
+ await using var conn = await OpenConnectionAsync(csb);
+
+ var tempTableName = await CreateTempTable(conn, "id int");
+
+ await using var batch = new NpgsqlBatch(conn);
+ for (var i = 0; i < 2; ++i)
+ {
+ for (var j = 0; j < 10; ++j)
+ {
+ batch.BatchCommands.Add(new NpgsqlBatchCommand($"DELETE FROM {tempTableName} WHERE 1=0"));
+ }
+ await batch.ExecuteNonQueryAsync();
+ batch.BatchCommands.Clear();
+ }
+ }
+
#endregion Miscellaneous
#region Logging
diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs
index 4f24eb5e18..993c151d25 100644
--- a/test/Npgsql.Tests/CommandTests.cs
+++ b/test/Npgsql.Tests/CommandTests.cs
@@ -339,6 +339,48 @@ public async Task Cancel_async_soft()
Assert.That(await conn.ExecuteScalarAsync("SELECT 1"), Is.EqualTo(1));
}
+ [Test, Description("Cancels an async query with the cancellation token and prepended query, with successful PG cancellation")]
+ [IssueLink("https://github.com/npgsql/npgsql/issues/5191")]
+ public async Task Cancel_async_soft_with_prepended_query()
+ {
+ if (IsMultiplexing)
+ return; // Multiplexing, cancellation
+
+ await using var postmasterMock = PgPostmasterMock.Start(ConnectionString);
+ using var _ = CreateTempPool(postmasterMock.ConnectionString, out var connectionString);
+ await using var conn = await OpenConnectionAsync(connectionString);
+ var server = await postmasterMock.WaitForServerConnection();
+
+ var processId = conn.ProcessID;
+
+ await using var tx = await conn.BeginTransactionAsync();
+ await using var cmd = CreateSleepCommand(conn);
+ using var cancellationSource = new CancellationTokenSource();
+ var t = cmd.ExecuteNonQueryAsync(cancellationSource.Token);
+
+ await server.ExpectSimpleQuery("BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED");
+ cancellationSource.Cancel();
+ await server
+ .WriteCommandComplete()
+ .WriteReadyForQuery(TransactionStatus.InTransactionBlock)
+ .FlushAsync();
+
+ Assert.That((await postmasterMock.WaitForCancellationRequest()).ProcessId,
+ Is.EqualTo(processId));
+
+ await server
+ .WriteErrorResponse(PostgresErrorCodes.QueryCanceled)
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ var exception = Assert.ThrowsAsync(async () => await t)!;
+ Assert.That(exception.InnerException,
+ Is.TypeOf().With.Property(nameof(PostgresException.SqlState)).EqualTo(PostgresErrorCodes.QueryCanceled));
+ Assert.That(exception.CancellationToken, Is.EqualTo(cancellationSource.Token));
+
+ Assert.That(conn.FullState, Is.EqualTo(ConnectionState.Open));
+ }
+
[Test, Description("Cancels an async query with the cancellation token, with unsuccessful PG cancellation (socket break)")]
public async Task Cancel_async_hard()
{
@@ -1006,6 +1048,99 @@ public async Task Use_across_connection_change([Values(PrepareOrNot.Prepared, Pr
Assert.That(await cmd.ExecuteScalarAsync(), Is.EqualTo(1));
}
+ [Test]
+ public async Task Parameter_overflow_message_length_throws()
+ {
+ // Create a separate dataSource because of Multiplexing (otherwise we can break unrelated queries)
+ await using var dataSource = CreateDataSource();
+ await using var conn = await dataSource.OpenConnectionAsync();
+ await using var cmd = new NpgsqlCommand("SELECT @a, @b, @c, @d, @e, @f, @g, @h", conn);
+
+ var largeParam = new string('A', 1 << 29);
+ cmd.Parameters.AddWithValue("a", largeParam);
+ cmd.Parameters.AddWithValue("b", largeParam);
+ cmd.Parameters.AddWithValue("c", largeParam);
+ cmd.Parameters.AddWithValue("d", largeParam);
+ cmd.Parameters.AddWithValue("e", largeParam);
+ cmd.Parameters.AddWithValue("f", largeParam);
+ cmd.Parameters.AddWithValue("g", largeParam);
+ cmd.Parameters.AddWithValue("h", largeParam);
+
+ Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync());
+ }
+
+ [Test]
+ public async Task Composite_overflow_message_length_throws()
+ {
+ await using var adminConnection = await OpenConnectionAsync();
+ var type = await GetTempTypeName(adminConnection);
+
+ await adminConnection.ExecuteNonQueryAsync(
+ $"CREATE TYPE {type} AS (a text, b text, c text, d text, e text, f text, g text, h text)");
+
+ var dataSourceBuilder = CreateDataSourceBuilder();
+ dataSourceBuilder.MapComposite(type);
+ await using var dataSource = dataSourceBuilder.Build();
+ await using var connection = await dataSource.OpenConnectionAsync();
+
+ var largeString = new string('A', 1 << 29);
+
+ await using var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT @a";
+ cmd.Parameters.AddWithValue("a", new BigComposite
+ {
+ A = largeString,
+ B = largeString,
+ C = largeString,
+ D = largeString,
+ E = largeString,
+ F = largeString,
+ G = largeString,
+ H = largeString
+ });
+
+ Assert.ThrowsAsync(async () => await cmd.ExecuteNonQueryAsync());
+ }
+
+ record BigComposite
+ {
+ public string A { get; set; } = null!;
+ public string B { get; set; } = null!;
+ public string C { get; set; } = null!;
+ public string D { get; set; } = null!;
+ public string E { get; set; } = null!;
+ public string F { get; set; } = null!;
+ public string G { get; set; } = null!;
+ public string H { get; set; } = null!;
+ }
+
+ [Test]
+ public async Task Array_overflow_message_length_throws()
+ {
+ // Create a separate dataSource because of Multiplexing (otherwise we can break unrelated queries)
+ await using var dataSource = CreateDataSource();
+ await using var conn = await dataSource.OpenConnectionAsync();
+
+ var largeString = new string('A', 1 << 29);
+
+ await using var cmd = conn.CreateCommand();
+ cmd.CommandText = "SELECT @a";
+ var array = new[]
+ {
+ largeString,
+ largeString,
+ largeString,
+ largeString,
+ largeString,
+ largeString,
+ largeString,
+ largeString
+ };
+ cmd.Parameters.AddWithValue("a", array);
+
+ Assert.ThrowsAsync(async () => await cmd.ExecuteNonQueryAsync());
+ }
+
[Test, Description("CreateCommand before connection open")]
[IssueLink("https://github.com/npgsql/npgsql/issues/565")]
public async Task Create_command_before_connection_open()
@@ -1098,11 +1233,15 @@ public async Task ExecuteReader_Throws_PostgresException([Values] bool async)
}
[Test]
- public void Command_is_recycled()
+ public void Command_is_recycled([Values] bool allResultTypesAreUnknown)
{
using var conn = OpenConnection();
var cmd1 = conn.CreateCommand();
cmd1.CommandText = "SELECT @p1";
+ if (allResultTypesAreUnknown)
+ cmd1.AllResultTypesAreUnknown = true;
+ else
+ cmd1.UnknownResultTypeList = [true];
var tx = conn.BeginTransaction();
cmd1.Transaction = tx;
cmd1.Parameters.AddWithValue("p1", 8);
@@ -1115,6 +1254,8 @@ public void Command_is_recycled()
Assert.That(cmd2.CommandType, Is.EqualTo(CommandType.Text));
Assert.That(cmd2.Transaction, Is.Null);
Assert.That(cmd2.Parameters, Is.Empty);
+ Assert.That(cmd2.AllResultTypesAreUnknown, Is.False);
+ Assert.That(cmd2.UnknownResultTypeList, Is.Null);
// TODO: Leaving this for now, since it'll be replaced by the new batching API
// Assert.That(cmd2.Statements, Is.Empty);
}
@@ -1181,6 +1322,7 @@ public async Task Too_many_parameters_throws([Values(PrepareOrNot.NotPrepared, P
sb.Append('@');
sb.Append(paramName);
}
+
cmd.CommandText = sb.ToString();
if (prepare == PrepareOrNot.Prepared)
@@ -1384,6 +1526,160 @@ await server
Assert.That(conn.FullState, Is.EqualTo(ConnectionState.Open));
}
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4804")]
+ [Description("Concurrent write and read failure can lead to deadlocks while cleaning up the connector.")]
+ public async Task Concurrent_read_write_failure_deadlock()
+ {
+ if (IsMultiplexing)
+ return;
+
+ await using var postmasterMock = PgPostmasterMock.Start(ConnectionString);
+ using var _ = CreateTempPool(postmasterMock.ConnectionString, out var connectionString);
+ await using var conn = await OpenConnectionAsync(connectionString);
+
+ await using var cmd = conn.CreateCommand();
+ // Attempt to send a big enough query to fill buffers
+ // That way the write side should be stuck, waiting for the server to empty buffers
+ cmd.CommandText = new string('a', 8_000_000);
+ var queryTask = cmd.ExecuteNonQueryAsync();
+
+ var server = await postmasterMock.WaitForServerConnection();
+ server.Close();
+
+ Assert.ThrowsAsync(async () => await queryTask);
+ }
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4906")]
+ [Description("Make sure we don't cancel a prepended query (and do not deadlock in case of a failure)")]
+ [Explicit("Flaky due to #5033")]
+ public async Task Not_cancel_prepended_query([Values] bool failPrependedQuery)
+ {
+ if (IsMultiplexing)
+ return;
+
+ await using var postmasterMock = PgPostmasterMock.Start(ConnectionString);
+ var csb = new NpgsqlConnectionStringBuilder(postmasterMock.ConnectionString)
+ {
+ NoResetOnClose = false
+ };
+ using var _ = CreateTempPool(csb, out var connectionString);
+ await using var conn = await OpenConnectionAsync(connectionString);
+ // reopen connection to append prepended query
+ await conn.CloseAsync();
+ await conn.OpenAsync();
+
+ using var cts = new CancellationTokenSource();
+ var queryTask = conn.ExecuteNonQueryAsync("SELECT 1", cancellationToken: cts.Token);
+
+ var server = await postmasterMock.WaitForServerConnection();
+ await server.ExpectSimpleQuery("DISCARD ALL");
+ await server.ExpectExtendedQuery();
+
+ var cancelTask = Task.Run(cts.Cancel);
+ var cancellationRequestTask = postmasterMock.WaitForCancellationRequest().AsTask();
+ // Give 1 second to make sure we didn't send cancellation request
+ await Task.Delay(1000);
+ Assert.IsFalse(cancelTask.IsCompleted);
+ Assert.IsFalse(cancellationRequestTask.IsCompleted);
+
+ if (failPrependedQuery)
+ {
+ await server
+ .WriteErrorResponse(PostgresErrorCodes.SyntaxError)
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await cancelTask;
+ await cancellationRequestTask;
+
+ Assert.ThrowsAsync(async () => await queryTask);
+ Assert.That(conn.State, Is.EqualTo(ConnectionState.Closed));
+ return;
+ }
+
+ await server
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await cancelTask;
+ await cancellationRequestTask;
+
+ await server
+ .WriteErrorResponse(PostgresErrorCodes.QueryCanceled)
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ Assert.ThrowsAsync(async () => await queryTask);
+
+ queryTask = conn.ExecuteNonQueryAsync("SELECT 1");
+ await server.ExpectExtendedQuery();
+ await server
+ .WriteParseComplete()
+ .WriteBindComplete()
+ .WriteNoData()
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .FlushAsync();
+ await queryTask;
+ }
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/5218")]
+ [Description("Make sure we do not lose unread messages after resetting oversize buffer")]
+ public async Task Oversize_buffer_lost_messages()
+ {
+ if (IsMultiplexing)
+ return;
+
+ var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ NoResetOnClose = true
+ };
+ await using var mock = PgPostmasterMock.Start(csb.ConnectionString);
+ await using var dataSource = NpgsqlDataSource.Create(mock.ConnectionString);
+ await using var connection = await dataSource.OpenConnectionAsync();
+ var connector = connection.Connector!;
+
+ var server = await mock.WaitForServerConnection();
+ await server
+ .WriteParseComplete()
+ .WriteBindComplete()
+ .WriteRowDescription(new FieldDescription(PostgresTypeOIDs.Text))
+ .WriteDataRowWithFlush(Encoding.ASCII.GetBytes(new string('a', connection.Settings.ReadBufferSize * 2)));
+ // Just to make sure we have enough space
+ await server.FlushAsync();
+ await server
+ .WriteDataRow(Encoding.ASCII.GetBytes("abc"))
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .WriteParameterStatus("SomeKey", "SomeValue")
+ .FlushAsync();
+
+ await using var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT 1";
+ await using (await cmd.ExecuteReaderAsync()) { }
+
+ await connection.CloseAsync();
+ await connection.OpenAsync();
+
+ Assert.AreSame(connector, connection.Connector);
+ // We'll get new value after the next query reads ParameterStatus from the buffer
+ Assert.That(connection.PostgresParameters, Does.Not.ContainKey("SomeKey").WithValue("SomeValue"));
+
+ await server
+ .WriteParseComplete()
+ .WriteBindComplete()
+ .WriteRowDescription(new FieldDescription(PostgresTypeOIDs.Text))
+ .WriteDataRow(Encoding.ASCII.GetBytes("abc"))
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await cmd.ExecuteNonQueryAsync();
+
+ Assert.That(connection.PostgresParameters, Contains.Key("SomeKey").WithValue("SomeValue"));
+ }
+
#region Logging
[Test]
diff --git a/test/Npgsql.Tests/CopyTests.cs b/test/Npgsql.Tests/CopyTests.cs
index adac0916f4..6a5e09c3f5 100644
--- a/test/Npgsql.Tests/CopyTests.cs
+++ b/test/Npgsql.Tests/CopyTests.cs
@@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Npgsql.Internal;
+using Npgsql.Tests.Support;
using NpgsqlTypes;
using NUnit.Framework;
using static Npgsql.Tests.TestUtil;
@@ -778,6 +779,25 @@ public async Task Binary_copy_throws_for_nullable()
Assert.ThrowsAsync(async () => await writer.WriteAsync(value, NpgsqlDbType.Integer));
}
+ [Test]
+ [IssueLink("https://github.com/npgsql/npgsql/issues/5110")]
+ public async Task Binary_copy_read_char_column()
+ {
+ await using var conn = await OpenConnectionAsync();
+ var tableName = await CreateTempTable(conn, "id serial, value char");
+
+ await using var cmd = conn.CreateCommand();
+ cmd.CommandText = $"INSERT INTO {tableName}(value) VALUES ('d'), ('s')";
+ await cmd.ExecuteNonQueryAsync();
+
+ await using var export = await conn.BeginBinaryExportAsync($"COPY {tableName}(id, value) TO STDOUT (FORMAT BINARY)");
+ while (await export.StartRowAsync() != -1)
+ {
+ var id = export.Read();
+ var value = export.Read();
+ }
+ }
+
#endregion
#region Text
@@ -1096,6 +1116,29 @@ public async Task Copy_is_not_supported_in_regular_command_execution()
Assert.That(() => conn.ExecuteNonQuery($@"COPY {table} (foo) FROM stdin"), Throws.Exception.TypeOf());
}
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/5209")]
+ [Platform(Exclude = "MacOsX", Reason = "Write might not throw an exception")]
+ public async Task RawBinaryCopy_write_nre([Values] bool async)
+ {
+ await using var postmasterMock = PgPostmasterMock.Start(ConnectionString);
+ await using var dataSource = NpgsqlDataSource.Create(postmasterMock.ConnectionString);
+ await using var conn = await dataSource.OpenConnectionAsync();
+
+ var server = await postmasterMock.WaitForServerConnection();
+ await server
+ .WriteCopyInResponse(isBinary: true)
+ .FlushAsync();
+
+ await using var stream = await conn.BeginRawBinaryCopyAsync("COPY SomeTable (field_text, field_int4) FROM STDIN");
+ server.Close();
+ var value = Encoding.UTF8.GetBytes(new string('a', conn.Settings.WriteBufferSize * 2));
+ if (async)
+ Assert.ThrowsAsync(async () => await stream.WriteAsync(value));
+ else
+ Assert.Throws(() => stream.Write(value));
+ Assert.That(conn.FullState, Is.EqualTo(ConnectionState.Broken));
+ }
+
#endregion
#region Utils
diff --git a/test/Npgsql.Tests/DataSourceTests.cs b/test/Npgsql.Tests/DataSourceTests.cs
index adbed90c0d..5c693a6eab 100644
--- a/test/Npgsql.Tests/DataSourceTests.cs
+++ b/test/Npgsql.Tests/DataSourceTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Data;
+using System.Data.Common;
using System.Threading.Tasks;
using NUnit.Framework;
@@ -243,4 +244,53 @@ public async Task Cannot_get_connection_after_dispose_unpooled([Values] bool asy
Assert.That(() => dataSource.OpenConnection(), Throws.Exception.TypeOf());
}
}
+
+ [Test] // #4752
+ public async Task As_DbDataSource([Values] bool async)
+ {
+ await using DbDataSource dataSource = NpgsqlDataSource.Create(ConnectionString);
+ await using var connection = async
+ ? await dataSource.OpenConnectionAsync()
+ : dataSource.OpenConnection();
+ Assert.That(connection.State, Is.EqualTo(ConnectionState.Open));
+
+ await using var command = dataSource.CreateCommand("SELECT 1");
+
+ Assert.That(async
+ ? await command.ExecuteScalarAsync()
+ : command.ExecuteScalar(), Is.EqualTo(1));
+ }
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4840")]
+ public async Task Multiplexing_connectionless_command_open_connection()
+ {
+ var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ Multiplexing = true
+ };
+ await using var dataSource = NpgsqlDataSource.Create(csb.ConnectionString);
+
+ await using var conn = await dataSource.OpenConnectionAsync();
+ await using var _ = await conn.BeginTransactionAsync();
+
+ await using var command = dataSource.CreateCommand();
+ command.CommandText = "SELECT 1";
+
+ await using var reader = await command.ExecuteReaderAsync();
+ Assert.True(reader.Read());
+ Assert.That(reader.GetInt32(0), Is.EqualTo(1));
+ }
+
+ [Test]
+ public async Task Connection_string_builder_settings_are_frozen_on_Build()
+ {
+ var builder = CreateDataSourceBuilder();
+ builder.ConnectionStringBuilder.ApplicationName = "foo";
+ await using var dataSource = builder.Build();
+
+ builder.ConnectionStringBuilder.ApplicationName = "bar";
+
+ await using var command = dataSource.CreateCommand("SHOW application_name");
+ Assert.That(await command.ExecuteScalarAsync(), Is.EqualTo("foo"));
+ }
}
diff --git a/test/Npgsql.Tests/DistributedTransactionTests.cs b/test/Npgsql.Tests/DistributedTransactionTests.cs
index 856861fa3a..0a45be8954 100644
--- a/test/Npgsql.Tests/DistributedTransactionTests.cs
+++ b/test/Npgsql.Tests/DistributedTransactionTests.cs
@@ -111,37 +111,6 @@ public void Two_connections_with_failure()
AssertNumberOfRows(adminConn, table, 0);
}
- [Test, IssueLink("https://github.com/npgsql/npgsql/issues/1737")]
- public void Multiple_unpooled_connections_do_not_reuse()
- {
- var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
- {
- Pooling = false,
- Enlist = true
- };
-
- using var scope = new TransactionScope();
-
- int processId;
-
- using (var conn1 = OpenConnection(csb))
- using (var cmd = new NpgsqlCommand("SELECT 1", conn1))
- {
- processId = conn1.ProcessID;
- cmd.ExecuteNonQuery();
- }
-
- using (var conn2 = OpenConnection(csb))
- using (var cmd = new NpgsqlCommand("SELECT 1", conn2))
- {
- // The connection reuse optimization isn't implemented for unpooled connections (though it could be)
- Assert.That(conn2.ProcessID, Is.Not.EqualTo(processId));
- cmd.ExecuteNonQuery();
- }
-
- scope.Complete();
- }
-
[Test(Description = "Transaction race, bool distributed")]
[Explicit("Fails on Appveyor (https://ci.appveyor.com/project/roji/npgsql/build/3.3.0-250)")]
public void Transaction_race([Values(false, true)] bool distributed)
@@ -395,6 +364,23 @@ public void Connection_reuse_race_chaining_transaction([Values(false, true)] boo
}
}
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/5246")]
+ public void Transaction_complete_with_undisposed_connections()
+ {
+ using var deleteOuter = new TransactionScope();
+ using (var delImidiate = new TransactionScope(TransactionScopeOption.RequiresNew))
+ {
+ var deleteNow = OpenConnection(ConnectionStringEnlistOn);
+ deleteNow.ExecuteNonQuery("SELECT 'del_now'");
+ var deleteNow2 = OpenConnection(ConnectionStringEnlistOn);
+ deleteNow2.ExecuteNonQuery("SELECT 'del_now2'");
+ delImidiate.Complete();
+ }
+ var deleteConn = OpenConnection(ConnectionStringEnlistOn);
+ deleteConn.ExecuteNonQuery("SELECT 'delete, this should commit last'");
+ deleteOuter.Complete();
+ }
+
#region Utilities
// MSDTC is asynchronous, i.e. Commit/Rollback may return before the transaction has actually completed in the database;
diff --git a/test/Npgsql.Tests/MultipleHostsTests.cs b/test/Npgsql.Tests/MultipleHostsTests.cs
index e588efbee6..f9e637d667 100644
--- a/test/Npgsql.Tests/MultipleHostsTests.cs
+++ b/test/Npgsql.Tests/MultipleHostsTests.cs
@@ -891,7 +891,6 @@ await firstServer
Assert.That(secondDataSource.GetDatabaseState(), Is.EqualTo(DatabaseState.PrimaryReadWrite));
}
- // This is the only test in this class which actually connects to PostgreSQL (the others use the PostgreSQL mock)
[Test, NonParallelizable]
public void IntegrationTest([Values] bool loadBalancing, [Values] bool alwaysCheckHostState)
{
@@ -953,6 +952,26 @@ async Task Query(NpgsqlDataSource dataSource)
}
}
+ [Test]
+ [IssueLink("https://github.com/npgsql/npgsql/issues/5055")]
+ [NonParallelizable] // Disables sql rewriting
+ public async Task Multiple_hosts_with_disabled_sql_rewriting()
+ {
+ using var _ = DisableSqlRewriting();
+
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(ConnectionString)
+ {
+ ConnectionStringBuilder =
+ {
+ Host = "localhost,127.0.0.1",
+ Pooling = true,
+ HostRecheckSeconds = 0
+ }
+ };
+ await using var dataSource = dataSourceBuilder.BuildMultiHost();
+ await using var conn = await dataSource.OpenConnectionAsync();
+ }
+
[Test]
public async Task DataSource_with_wrappers()
{
diff --git a/test/Npgsql.Tests/NotificationTests.cs b/test/Npgsql.Tests/NotificationTests.cs
index 0092dfdad4..09b56b0e44 100644
--- a/test/Npgsql.Tests/NotificationTests.cs
+++ b/test/Npgsql.Tests/NotificationTests.cs
@@ -212,4 +212,24 @@ public void WaitAsync_breaks_connection()
Assert.That(pgEx.SqlState, Is.EqualTo(PostgresErrorCodes.AdminShutdown));
Assert.That(conn.FullState, Is.EqualTo(ConnectionState.Broken));
}
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4911")]
+ public async Task Big_notice_while_loading_types()
+ {
+ await using var adminConn = await OpenConnectionAsync();
+ // Max notification payload is 8000
+ var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ ReadBufferSize = 4096
+ };
+ using var _ = CreateTempPool(csb, out var connString);
+ await using var conn = await OpenConnectionAsync(connString);
+
+ var notify = GetUniqueIdentifier(nameof(Big_notice_while_loading_types));
+ await conn.ExecuteNonQueryAsync($"LISTEN {notify}");
+ var payload = new string('a', 5000);
+ await adminConn.ExecuteNonQueryAsync($"NOTIFY {notify}, '{payload}'");
+
+ await conn.ReloadTypesAsync();
+ }
}
diff --git a/test/Npgsql.Tests/PoolTests.cs b/test/Npgsql.Tests/PoolTests.cs
index 2929884306..c2c84f9372 100644
--- a/test/Npgsql.Tests/PoolTests.cs
+++ b/test/Npgsql.Tests/PoolTests.cs
@@ -270,6 +270,52 @@ public void Prune_idle_connectors(int minPoolSize, int connectionIdleLifeTime, i
AssertPoolState(pool, open: Math.Max(1, minPoolSize), idle: Math.Max(0, minPoolSize - 1));
}
+ [Test]
+ [Explicit("Timing-based")]
+ public async Task Prune_counts_max_lifetime_exceeded()
+ {
+ var connString = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ MinPoolSize = 0,
+ // Idle lifetime 2 seconds, 2 samples
+ ConnectionIdleLifetime = 2,
+ ConnectionPruningInterval = 1,
+ ConnectionLifetime = 5
+ }.ToString();
+
+ await using var dataSource = NpgsqlDataSource.Create(connString);
+
+ // conn1 will exceed max lifetime
+ await using var conn1 = await dataSource.OpenConnectionAsync();
+
+ // make conn1 4 seconds older than the others, so it exceeds max lifetime
+ Thread.Sleep(4000);
+
+ await using var conn2 = await dataSource.OpenConnectionAsync();
+ await using var conn3 = await dataSource.OpenConnectionAsync();
+
+ await conn1.CloseAsync();
+ await conn2.CloseAsync();
+ AssertPoolState(dataSource, open: 3, idle: 2);
+
+ // wait for 1 sample
+ Thread.Sleep(1000);
+ // ConnectionIdleLifetime not yet reached.
+ AssertPoolState(dataSource, open: 3, idle: 2);
+
+ // close conn3, so we can see if too many connectors get pruned
+ await conn3.CloseAsync();
+
+ // wait for last sample + a bit more time for reliability
+ Thread.Sleep(1500);
+
+ // ConnectionIdleLifetime reached
+ // - conn1 should have been closed due to max lifetime (but this should count as pruning)
+ // - conn2 or conn3 should have been closed due to idle pruning
+ // - conn3 or conn2 should remain
+ AssertPoolState(dataSource, open: 1, idle: 1);
+ }
+
[Test, Description("Makes sure that when a waiting async open is is given a connection, the continuation is executed in the TP rather than on the closing thread")]
public void Close_releases_waiter_on_another_thread()
{
diff --git a/test/Npgsql.Tests/PrepareTests.cs b/test/Npgsql.Tests/PrepareTests.cs
index 12f8d2e3b5..8cc3122a49 100644
--- a/test/Npgsql.Tests/PrepareTests.cs
+++ b/test/Npgsql.Tests/PrepareTests.cs
@@ -5,6 +5,8 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Npgsql.BackendMessages;
+using Npgsql.Tests.Support;
using NpgsqlTypes;
using NUnit.Framework;
using static Npgsql.Tests.TestUtil;
@@ -13,6 +15,8 @@ namespace Npgsql.Tests;
public class PrepareTests: TestBase
{
+ const int Int4Oid = 23;
+
[Test]
public void Basic()
{
@@ -750,7 +754,7 @@ public void Multiplexing_not_supported()
}
[Test]
- public async Task Explicitly_prepared_statement_invalidation()
+ public async Task Explicitly_prepared_statement_invalidation([Values] bool prepareAfterError, [Values] bool unprepareAfterError)
{
var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
{
@@ -769,12 +773,167 @@ public async Task Explicitly_prepared_statement_invalidation()
// Since we've changed the table schema, the next execution of the prepared statement will error with 0A000
var exception = Assert.ThrowsAsync(() => command.ExecuteNonQueryAsync())!;
Assert.That(exception.SqlState, Is.EqualTo(PostgresErrorCodes.FeatureNotSupported)); // cached plan must not change result type
+ Assert.IsFalse(command.IsPrepared);
+
+ if (unprepareAfterError)
+ {
+ // Just check that calling unprepare after error doesn't break anything
+ await command.UnprepareAsync();
+ Assert.IsFalse(command.IsPrepared);
+ }
+
+ if (prepareAfterError)
+ {
+ // If we explicitly prepare after error, we should replace the previous prepared statement with a new one
+ await command.PrepareAsync();
+ Assert.IsTrue(command.IsPrepared);
+ }
// However, Npgsql should invalidate the prepared statement in this case, so the next execution should work
Assert.DoesNotThrowAsync(() => command.ExecuteNonQueryAsync());
- // The command is unprepared, though. It's the user's responsibility to re-prepare if they wish.
- Assert.False(command.IsPrepared);
+ if (!prepareAfterError)
+ {
+ // The command is unprepared, though. It's the user's responsibility to re-prepare if they wish.
+ Assert.False(command.IsPrepared);
+ }
+ }
+
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4920")]
+ public async Task Explicit_prepare_unprepare_many_queries()
+ {
+ // Set a specific buffer's size to trigger #4920
+ var builder = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ WriteBufferSize = 5002
+ };
+ await using var conn = await OpenConnectionAsync(builder);
+ await using var cmd = conn.CreateCommand();
+
+ cmd.CommandText = string.Join(';', Enumerable.Range(1, 500).Select(x => $"SELECT {x}"));
+ await cmd.PrepareAsync();
+ await cmd.UnprepareAsync();
+ }
+
+ [Test]
+ public async Task Explicitly_prepared_batch_sends_prepared_queries()
+ {
+ await using var postmaster = PgPostmasterMock.Start(ConnectionString);
+ await using var dataSource = NpgsqlDataSource.Create(postmaster.ConnectionString);
+
+ await using var conn = await dataSource.OpenConnectionAsync();
+ var server = await postmaster.WaitForServerConnection();
+
+ await using var batch = new NpgsqlBatch(conn)
+ {
+ BatchCommands = { new("SELECT 1"), new("SELECT 2") }
+ };
+
+ var prepareTask = batch.PrepareAsync();
+
+ await server.ExpectMessages(
+ FrontendMessageCode.Parse, FrontendMessageCode.Describe,
+ FrontendMessageCode.Parse, FrontendMessageCode.Describe,
+ FrontendMessageCode.Sync);
+
+ await server
+ .WriteParseComplete()
+ .WriteParameterDescription(new FieldDescription(Int4Oid))
+ .WriteRowDescription(new FieldDescription(Int4Oid))
+ .WriteParseComplete()
+ .WriteParameterDescription(new FieldDescription(Int4Oid))
+ .WriteRowDescription(new FieldDescription(Int4Oid))
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await prepareTask;
+
+ for (var i = 0; i < 2; i++)
+ await ExecutePreparedBatch(batch, server);
+
+ async Task ExecutePreparedBatch(NpgsqlBatch batch, PgServerMock server)
+ {
+ var executeBatchTask = batch.ExecuteNonQueryAsync();
+
+ await server.ExpectMessages(
+ FrontendMessageCode.Bind, FrontendMessageCode.Execute,
+ FrontendMessageCode.Bind, FrontendMessageCode.Execute,
+ FrontendMessageCode.Sync);
+
+ await server
+ .WriteBindComplete()
+ .WriteCommandComplete()
+ .WriteBindComplete()
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await executeBatchTask;
+ }
+ }
+
+ [Test]
+ public async Task Auto_prepared_batch_sends_prepared_queries()
+ {
+ var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ AutoPrepareMinUsages = 1,
+ MaxAutoPrepare = 10
+ };
+ await using var postmaster = PgPostmasterMock.Start(csb.ConnectionString);
+ await using var dataSource = NpgsqlDataSource.Create(postmaster.ConnectionString);
+
+ await using var conn = await dataSource.OpenConnectionAsync();
+ var server = await postmaster.WaitForServerConnection();
+
+ await using var batch = new NpgsqlBatch(conn)
+ {
+ BatchCommands = { new("SELECT 1"), new("SELECT 2") }
+ };
+
+ var firstBatchExecuteTask = batch.ExecuteNonQueryAsync();
+
+ await server.ExpectMessages(
+ FrontendMessageCode.Parse, FrontendMessageCode.Bind, FrontendMessageCode.Describe, FrontendMessageCode.Execute,
+ FrontendMessageCode.Parse, FrontendMessageCode.Bind, FrontendMessageCode.Describe, FrontendMessageCode.Execute,
+ FrontendMessageCode.Sync);
+
+ await server
+ .WriteParseComplete()
+ .WriteBindComplete()
+ .WriteRowDescription(new FieldDescription(Int4Oid))
+ .WriteCommandComplete()
+ .WriteParseComplete()
+ .WriteBindComplete()
+ .WriteRowDescription(new FieldDescription(Int4Oid))
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await firstBatchExecuteTask;
+
+ for (var i = 0; i < 2; i++)
+ await ExecutePreparedBatch(batch, server);
+
+ async Task ExecutePreparedBatch(NpgsqlBatch batch, PgServerMock server)
+ {
+ var executeBatchTask = batch.ExecuteNonQueryAsync();
+
+ await server.ExpectMessages(
+ FrontendMessageCode.Bind, FrontendMessageCode.Execute,
+ FrontendMessageCode.Bind, FrontendMessageCode.Execute,
+ FrontendMessageCode.Sync);
+
+ await server
+ .WriteBindComplete()
+ .WriteCommandComplete()
+ .WriteBindComplete()
+ .WriteCommandComplete()
+ .WriteReadyForQuery()
+ .FlushAsync();
+
+ await executeBatchTask;
+ }
}
NpgsqlConnection OpenConnectionAndUnprepare(string? connectionString = null)
diff --git a/test/Npgsql.Tests/ReaderOldSchemaTests.cs b/test/Npgsql.Tests/ReaderOldSchemaTests.cs
index 92e3cf2e6d..edbeb15842 100644
--- a/test/Npgsql.Tests/ReaderOldSchemaTests.cs
+++ b/test/Npgsql.Tests/ReaderOldSchemaTests.cs
@@ -118,32 +118,18 @@ await conn.ExecuteNonQueryAsync($@"
CREATE TABLE {table} (id SERIAL PRIMARY KEY, int2 SMALLINT);
CREATE OR REPLACE VIEW {view} (id, int2) AS SELECT id, int2 + int2 AS int2 FROM {table}");
- var command = new NpgsqlCommand($"SELECT * FROM {view}", conn);
+ var command = new NpgsqlCommand($"SELECT id, int2 FROM {view}", conn);
- using var dr = command.ExecuteReader();
+ using var dr = command.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
var metadata = await GetSchemaTable(dr);
- foreach (var r in metadata!.Rows.OfType())
- {
- switch ((string)r["ColumnName"])
- {
- case "field_pk":
- if (conn.PostgreSqlVersion < new Version("9.4"))
- {
- // 9.3 and earlier: IsUpdatable = False
- Assert.IsTrue((bool)r["IsReadonly"], "field_pk");
- }
- else
- {
- // 9.4: IsUpdatable = True
- Assert.IsFalse((bool)r["IsReadonly"], "field_pk");
- }
- break;
- case "field_int2":
- Assert.IsTrue((bool)r["IsReadonly"]);
- break;
- }
- }
+ var idRow = metadata!.Rows.OfType().FirstOrDefault(x => (string)x["ColumnName"] == "id");
+ Assert.IsNotNull(idRow, "Unable to find metadata for id column");
+ var int2Row = metadata.Rows.OfType().FirstOrDefault(x => (string)x["ColumnName"] == "int2");
+ Assert.IsNotNull(int2Row, "Unable to find metadata for int2 column");
+
+ Assert.IsFalse((bool)idRow!["IsReadonly"]);
+ Assert.IsTrue((bool)int2Row!["IsReadonly"]);
}
// ReSharper disable once InconsistentNaming
@@ -156,19 +142,14 @@ public async Task AllowDBNull()
using var cmd = new NpgsqlCommand($"SELECT * FROM {table}", conn);
using var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
using var metadata = await GetSchemaTable(reader);
- foreach (var row in metadata!.Rows.OfType())
- {
- var isNullable = (bool)row["AllowDBNull"];
- switch ((string)row["ColumnName"])
- {
- case "nullable":
- Assert.IsTrue(isNullable);
- continue;
- case "non_nullable":
- Assert.IsFalse(isNullable);
- continue;
- }
- }
+
+ var nullableRow = metadata!.Rows.OfType().FirstOrDefault(x => (string)x["ColumnName"] == "nullable");
+ Assert.IsNotNull(nullableRow, "Unable to find metadata for nullable column");
+ var nonNullableRow = metadata.Rows.OfType().FirstOrDefault(x => (string)x["ColumnName"] == "non_nullable");
+ Assert.IsNotNull(nonNullableRow, "Unable to find metadata for non_nullable column");
+
+ Assert.IsTrue((bool)nullableRow!["AllowDBNull"]);
+ Assert.IsFalse((bool)nonNullableRow!["AllowDBNull"]);
}
[Test, IssueLink("https://github.com/npgsql/npgsql/issues/1027")]
diff --git a/test/Npgsql.Tests/ReaderTests.cs b/test/Npgsql.Tests/ReaderTests.cs
index 5dc6bd6534..ebd3a89c13 100644
--- a/test/Npgsql.Tests/ReaderTests.cs
+++ b/test/Npgsql.Tests/ReaderTests.cs
@@ -1234,6 +1234,24 @@ await pgMock
Assert.ThrowsAsync(async () => await reader.DisposeAsync());
}
+ [Test]
+ public async Task Read_string_as_char()
+ {
+ await using var conn = await OpenConnectionAsync();
+
+ await using var cmd = conn.CreateCommand();
+ cmd.CommandText = "SELECT 'abcdefgh', 'ijklmnop'";
+
+ await using var reader = await cmd.ExecuteReaderAsync(Behavior);
+ Assert.IsTrue(await reader.ReadAsync());
+ Assert.That(reader.GetChar(0), Is.EqualTo('a'));
+ if (Behavior == CommandBehavior.SequentialAccess)
+ Assert.Throws(() => reader.GetChar(0));
+ else
+ Assert.That(reader.GetChar(0), Is.EqualTo('a'));
+ Assert.That(reader.GetChar(1), Is.EqualTo('i'));
+ }
+
#region GetBytes / GetStream
[Test]
@@ -1453,6 +1471,50 @@ public async Task GetStream_in_middle_of_column_throws([Values] bool async)
Assert.That(() => reader.GetStream(0), Throws.Exception.TypeOf());
}
+ [Test, IssueLink("https://github.com/npgsql/npgsql/issues/5223")]
+ public async Task GetStream_seek()
+ {
+ // Sequential doesn't allow to seek
+ if (IsSequential)
+ return;
+
+ await using var conn = await OpenConnectionAsync();
+ await using var cmd = conn.CreateCommand();
+ cmd.CommandText = "SELECT 'abcdefgh'";
+ await using var reader = await cmd.ExecuteReaderAsync();
+ await reader.ReadAsync();
+
+ var buffer = new byte[4];
+
+ await using var stream = reader.GetStream(0);
+ Assert.IsTrue(stream.CanSeek);
+
+ var seekPosition = stream.Seek(-1, SeekOrigin.End);
+ Assert.That(seekPosition, Is.EqualTo(stream.Length - 1));
+ var read = stream.Read(buffer);
+ Assert.That(read, Is.EqualTo(1));
+ Assert.That(Encoding.ASCII.GetString(buffer, 0, 1), Is.EqualTo("h"));
+ read = stream.Read(buffer);
+ Assert.That(read, Is.EqualTo(0));
+
+ seekPosition = stream.Seek(2, SeekOrigin.Begin);
+ Assert.That(seekPosition, Is.EqualTo(2));
+ read = stream.Read(buffer);
+ Assert.That(read, Is.EqualTo(buffer.Length));
+ Assert.That(Encoding.ASCII.GetString(buffer), Is.EqualTo("cdef"));
+
+ seekPosition = stream.Seek(-3, SeekOrigin.Current);
+ Assert.That(seekPosition, Is.EqualTo(3));
+ read = stream.Read(buffer);
+ Assert.That(read, Is.EqualTo(buffer.Length));
+ Assert.That(Encoding.ASCII.GetString(buffer), Is.EqualTo("defg"));
+
+ stream.Position = 1;
+ read = stream.Read(buffer);
+ Assert.That(read, Is.EqualTo(buffer.Length));
+ Assert.That(Encoding.ASCII.GetString(buffer), Is.EqualTo("bcde"));
+ }
+
#endregion GetBytes / GetStream
#region GetChars / GetTextReader
diff --git a/test/Npgsql.Tests/Replication/PgOutputReplicationTests.cs b/test/Npgsql.Tests/Replication/PgOutputReplicationTests.cs
index d8fd2ed3a2..93b1afcba1 100644
--- a/test/Npgsql.Tests/Replication/PgOutputReplicationTests.cs
+++ b/test/Npgsql.Tests/Replication/PgOutputReplicationTests.cs
@@ -641,10 +641,12 @@ await c.ExecuteNonQueryAsync(@$"
await NextMessage(messages);
}, nameof(Dispose_while_replicating));
- [TestCase(true)]
- [TestCase(false)]
+ [Platform(Exclude = "MacOsX", Reason = "Test is flaky in CI on Mac, see https://github.com/npgsql/npgsql/issues/5294")]
+ [TestCase(true, true)]
+ [TestCase(true, false)]
+ [TestCase(false, false)]
[Test(Description = "Tests whether logical decoding messages get replicated as Logical Replication Protocol Messages on PostgreSQL 14 and above")]
- public Task LogicalDecodingMessage(bool writeMessages)
+ public Task LogicalDecodingMessage(bool writeMessages, bool readMessages)
=> SafeReplicationTest(
async (slotName, tableName, publicationName) =>
{
@@ -689,9 +691,12 @@ public Task LogicalDecodingMessage(bool writeMessages)
Assert.That(msg.Flags, Is.EqualTo(1));
Assert.That(msg.Prefix, Is.EqualTo(prefix));
Assert.That(msg.Data.Length, Is.EqualTo(transactionalMessage.Length));
- var buffer = new MemoryStream();
- await msg.Data.CopyToAsync(buffer, CancellationToken.None);
- Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(transactionalMessage));
+ if (readMessages)
+ {
+ var buffer = new MemoryStream();
+ await msg.Data.CopyToAsync(buffer, CancellationToken.None);
+ Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(transactionalMessage));
+ }
}
// Relation
@@ -712,9 +717,12 @@ public Task LogicalDecodingMessage(bool writeMessages)
Assert.That(msg.Flags, Is.EqualTo(0));
Assert.That(msg.Prefix, Is.EqualTo(prefix));
Assert.That(msg.Data.Length, Is.EqualTo(nonTransactionalMessage.Length));
- var buffer = new MemoryStream();
- await msg.Data.CopyToAsync(buffer, CancellationToken.None);
- Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(nonTransactionalMessage));
+ if (readMessages)
+ {
+ var buffer = new MemoryStream();
+ await msg.Data.CopyToAsync(buffer, CancellationToken.None);
+ Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(nonTransactionalMessage));
+ }
}
if (IsStreaming)
@@ -737,9 +745,12 @@ public Task LogicalDecodingMessage(bool writeMessages)
Assert.That(msg.Flags, Is.EqualTo(1));
Assert.That(msg.Prefix, Is.EqualTo(prefix));
Assert.That(msg.Data.Length, Is.EqualTo(transactionalMessage.Length));
- var buffer = new MemoryStream();
- await msg.Data.CopyToAsync(buffer, CancellationToken.None);
- Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(transactionalMessage));
+ if (readMessages)
+ {
+ var buffer = new MemoryStream();
+ await msg.Data.CopyToAsync(buffer, CancellationToken.None);
+ Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(transactionalMessage));
+ }
}
// Further inserts
@@ -767,9 +778,13 @@ public Task LogicalDecodingMessage(bool writeMessages)
Assert.That(msg.Flags, Is.EqualTo(0));
Assert.That(msg.Prefix, Is.EqualTo(prefix));
Assert.That(msg.Data.Length, Is.EqualTo(nonTransactionalMessage.Length));
- var buffer = new MemoryStream();
- await msg.Data.CopyToAsync(buffer, CancellationToken.None);
- Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(nonTransactionalMessage));
+ if (readMessages)
+ {
+ var buffer = new MemoryStream();
+ await msg.Data.CopyToAsync(buffer, CancellationToken.None);
+ Assert.That(rc.Encoding.GetString(buffer.ToArray()), Is.EqualTo(nonTransactionalMessage));
+ }
+
if (IsStreaming)
await messages.MoveNextAsync();
}
diff --git a/test/Npgsql.Tests/SchemaTests.cs b/test/Npgsql.Tests/SchemaTests.cs
index 83f9e859c6..5deee67a0d 100644
--- a/test/Npgsql.Tests/SchemaTests.cs
+++ b/test/Npgsql.Tests/SchemaTests.cs
@@ -380,11 +380,16 @@ public async Task Unique_constraint()
Assert.That(columns.All(r => r["table_name"].Equals(table)));
Assert.That(columns.All(r => r["constraint_type"].Equals("UNIQUE KEY")));
- Assert.That(columns[0]["column_name"], Is.EqualTo("f1"));
- Assert.That(columns[0]["ordinal_number"], Is.EqualTo(1));
+ Assert.That(columns.Count, Is.EqualTo(2));
- Assert.That(columns[1]["column_name"], Is.EqualTo("f2"));
- Assert.That(columns[1]["ordinal_number"], Is.EqualTo(2));
+ // Columns are not necessarily in the correct order
+ var firstColumn = columns.FirstOrDefault(x => (string)x["column_name"] == "f1")!;
+ Assert.NotNull(firstColumn);
+ Assert.That(firstColumn["ordinal_number"], Is.EqualTo(1));
+
+ var secondColumn = columns.FirstOrDefault(x => (string)x["column_name"] == "f2")!;
+ Assert.NotNull(secondColumn);
+ Assert.That(secondColumn["ordinal_number"], Is.EqualTo(2));
}
[Test]
diff --git a/test/Npgsql.Tests/Support/PgPostmasterMock.cs b/test/Npgsql.Tests/Support/PgPostmasterMock.cs
index 7cc33c1877..f9a5e4ad95 100644
--- a/test/Npgsql.Tests/Support/PgPostmasterMock.cs
+++ b/test/Npgsql.Tests/Support/PgPostmasterMock.cs
@@ -139,6 +139,7 @@ async Task Accept(bool completeCancellationImmediat
var readBuffer = new NpgsqlReadBuffer(null!, stream, clientSocket, ReadBufferSize, Encoding,
RelaxedEncoding);
var writeBuffer = new NpgsqlWriteBuffer(null!, stream, clientSocket, WriteBufferSize, Encoding);
+ writeBuffer.MessageLengthValidation = false;
await readBuffer.EnsureAsync(4);
var len = readBuffer.ReadInt32();
diff --git a/test/Npgsql.Tests/Support/PgServerMock.cs b/test/Npgsql.Tests/Support/PgServerMock.cs
index 639124be5c..c3cf70dd7f 100644
--- a/test/Npgsql.Tests/Support/PgServerMock.cs
+++ b/test/Npgsql.Tests/Support/PgServerMock.cs
@@ -38,6 +38,7 @@ internal PgServerMock(
_stream = stream;
_readBuffer = readBuffer;
_writeBuffer = writeBuffer;
+ writeBuffer.MessageLengthValidation = false;
}
internal async Task Startup(MockState state)
@@ -225,6 +226,20 @@ internal PgServerMock WriteRowDescription(params FieldDescription[] fields)
return this;
}
+ internal PgServerMock WriteParameterDescription(params FieldDescription[] fields)
+ {
+ CheckDisposed();
+
+ _writeBuffer.WriteByte((byte)BackendMessageCode.ParameterDescription);
+ _writeBuffer.WriteInt32(1 + 4 + 2 + fields.Length * 4);
+ _writeBuffer.WriteUInt16((ushort)fields.Length);
+
+ foreach (var field in fields)
+ _writeBuffer.WriteUInt32(field.TypeOID);
+
+ return this;
+ }
+
internal PgServerMock WriteNoData()
{
CheckDisposed();
@@ -328,12 +343,12 @@ internal PgServerMock WriteBackendKeyData(int processId, int secret)
internal PgServerMock WriteCancellationResponse()
=> WriteErrorResponse(PostgresErrorCodes.QueryCanceled, "Cancellation", "Query cancelled");
- internal PgServerMock WriteCopyInResponse()
+ internal PgServerMock WriteCopyInResponse(bool isBinary = false)
{
CheckDisposed();
_writeBuffer.WriteByte((byte)BackendMessageCode.CopyInResponse);
_writeBuffer.WriteInt32(5);
- _writeBuffer.WriteByte(0);
+ _writeBuffer.WriteByte(isBinary ? (byte)1 : (byte)0);
_writeBuffer.WriteInt16(1);
_writeBuffer.WriteInt16(0);
return this;
@@ -381,4 +396,4 @@ public void Dispose()
_disposed = true;
}
-}
\ No newline at end of file
+}
diff --git a/test/Npgsql.Tests/SystemTransactionTests.cs b/test/Npgsql.Tests/SystemTransactionTests.cs
index 27a9d057e1..3150e2cb83 100644
--- a/test/Npgsql.Tests/SystemTransactionTests.cs
+++ b/test/Npgsql.Tests/SystemTransactionTests.cs
@@ -293,6 +293,32 @@ public void Single_unpooled_connection()
scope.Complete();
}
+ [Test]
+ [IssueLink("https://github.com/npgsql/npgsql/issues/4963"), IssueLink("https://github.com/npgsql/npgsql/issues/5783")]
+ public void Single_closed_connection_in_transaction_scope([Values] bool pooling, [Values] bool multipleHosts)
+ {
+ var csb = new NpgsqlConnectionStringBuilder(ConnectionString)
+ {
+ Pooling = false,
+ Enlist = true
+ };
+ if (multipleHosts)
+ csb.Host = "localhost,127.0.0.1";
+ using var dataSource = NpgsqlDataSource.Create(csb);
+
+ using (var scope = new TransactionScope())
+ using (var conn = dataSource.OpenConnection())
+ using (var cmd = new NpgsqlCommand("SELECT 1", conn))
+ {
+ cmd.ExecuteNonQuery();
+ conn.Close();
+ Assert.That(pooling ? dataSource.Statistics.Busy : dataSource.Statistics.Total, Is.EqualTo(1));
+ scope.Complete();
+ }
+
+ Assert.That(pooling ? dataSource.Statistics.Busy : dataSource.Statistics.Total, Is.EqualTo(0));
+ }
+
[Test]
[IssueLink("https://github.com/npgsql/npgsql/issues/3863")]
public void Break_connector_while_in_transaction_scope_with_rollback([Values] bool pooling)
diff --git a/test/Npgsql.Tests/TestUtil.cs b/test/Npgsql.Tests/TestUtil.cs
index ecfdd85ff3..769f7f9522 100644
--- a/test/Npgsql.Tests/TestUtil.cs
+++ b/test/Npgsql.Tests/TestUtil.cs
@@ -367,6 +367,9 @@ internal static IDisposable SetCurrentCulture(CultureInfo culture)
internal static IDisposable DisableSqlRewriting()
{
#if DEBUG
+ // We clear the pools to make sure we don't accidentally reuse a pool
+ // Since EnableSqlRewriting is a global change
+ PoolManager.Reset();
NpgsqlCommand.EnableSqlRewriting = false;
return new DeferredExecutionDisposable(() => NpgsqlCommand.EnableSqlRewriting = true);
#else
diff --git a/test/Npgsql.Tests/Types/DateTimeTests.cs b/test/Npgsql.Tests/Types/DateTimeTests.cs
index e91cf5c7f2..87c8cbb5f2 100644
--- a/test/Npgsql.Tests/Types/DateTimeTests.cs
+++ b/test/Npgsql.Tests/Types/DateTimeTests.cs
@@ -16,6 +16,10 @@ public class DateTimeTests : TestBase
public Task Date_as_DateTime()
=> AssertType(new DateTime(2020, 10, 1), "2020-10-01", "date", NpgsqlDbType.Date, DbType.Date, isDefaultForWriting: false);
+ [Test]
+ public Task Date_as_DateTime_with_date_and_time_before_2000()
+ => AssertTypeWrite(new DateTime(1980, 10, 1, 11, 0, 0), "1980-10-01", "date", NpgsqlDbType.Date, DbType.Date, isDefault: false);
+
// Internal PostgreSQL representation (days since 2020-01-01), for out-of-range values.
[Test]
public Task Date_as_int()
diff --git a/test/Npgsql.Tests/Types/GeometricTypeTests.cs b/test/Npgsql.Tests/Types/GeometricTypeTests.cs
index b5948b0e66..b4101cca14 100644
--- a/test/Npgsql.Tests/Types/GeometricTypeTests.cs
+++ b/test/Npgsql.Tests/Types/GeometricTypeTests.cs
@@ -61,4 +61,4 @@ public Task Circle()
NpgsqlDbType.Circle);
public GeometricTypeTests(MultiplexingMode multiplexingMode) : base(multiplexingMode) {}
-}
\ No newline at end of file
+}
diff --git a/test/Npgsql.Tests/TypesTests.cs b/test/Npgsql.Tests/TypesTests.cs
index 690250aa68..0d6ae96b15 100644
--- a/test/Npgsql.Tests/TypesTests.cs
+++ b/test/Npgsql.Tests/TypesTests.cs
@@ -185,6 +185,14 @@ public void TsQueryOperatorPrecedence()
Assert.AreEqual(expectedGrouping.ToString(), query.ToString());
}
+ [Test]
+ public void NpgsqlPath_empty()
+ => Assert.That(new NpgsqlPath { new(1, 2) }, Is.EqualTo(new NpgsqlPath(new NpgsqlPoint(1, 2))));
+
+ [Test]
+ public void NpgsqlPolygon_empty()
+ => Assert.That(new NpgsqlPolygon { new(1, 2) }, Is.EqualTo(new NpgsqlPolygon(new NpgsqlPoint(1, 2))));
+
[Test]
public void Bug1011018()
{