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 ReadSingleRow(string command, CancellationToken cancellatio results[i] = buf.ReadString(len); continue; case "integer": - { - var str = buf.ReadString(len); - if (!uint.TryParse(str, NumberStyles.None, null, out var num)) - { - throw Connector.Break( - new NpgsqlException( - $"Could not parse '{str}' as unsigned integer in field {field.Name}")); - } - - results[i] = num; - continue; - } case "bigint": { var str = buf.ReadString(len); - if (!ulong.TryParse(str, NumberStyles.None, null, out var num)) + if (!uint.TryParse(str, NumberStyles.None, null, out var num)) { throw Connector.Break( new NpgsqlException( @@ -958,4 +947,4 @@ internal void CheckDisposed() if (_isDisposed) throw new ObjectDisposedException(GetType().Name); } -} \ No newline at end of file +} diff --git a/src/Npgsql/Shims/DbDataSource.cs b/src/Npgsql/Shims/DbDataSource.cs index fd720bb65b..6951d427fb 100644 --- a/src/Npgsql/Shims/DbDataSource.cs +++ b/src/Npgsql/Shims/DbDataSource.cs @@ -40,7 +40,7 @@ public ValueTask OpenConnectionAsync(CancellationToken cancellatio => OpenDbConnectionAsync(cancellationToken); public DbCommand CreateCommand(string? commandText = null) - => CreateDbCommand(); + => CreateDbCommand(commandText); public DbBatch CreateBatch() => CreateDbBatch(); diff --git a/src/Npgsql/UnpooledDataSource.cs b/src/Npgsql/UnpooledDataSource.cs index a1ff6659bd..8226524635 100644 --- a/src/Npgsql/UnpooledDataSource.cs +++ b/src/Npgsql/UnpooledDataSource.cs @@ -48,13 +48,4 @@ internal override void Return(NpgsqlConnector connector) } internal override void Clear() {} - - internal override bool TryRentEnlistedPending(Transaction transaction, NpgsqlConnection connection, - [NotNullWhen(true)] out NpgsqlConnector? connector) - { - connector = null; - return false; - } - - internal override bool TryRemovePendingEnlistedConnector(NpgsqlConnector connector, Transaction transaction) => false; } \ No newline at end of file diff --git a/src/Npgsql/Util/ResettableCancellationTokenSource.cs b/src/Npgsql/Util/ResettableCancellationTokenSource.cs index c61a07b99e..9bb507b1cb 100644 --- a/src/Npgsql/Util/ResettableCancellationTokenSource.cs +++ b/src/Npgsql/Util/ResettableCancellationTokenSource.cs @@ -48,10 +48,21 @@ public CancellationToken Start(CancellationToken cancellationToken = default) #if DEBUG Debug.Assert(!_isRunning); #endif - _cts.CancelAfter(Timeout); - if (_cts.IsCancellationRequested) + lock (lockObject) { - 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) + { +#if DEBUG + _isRunning = true; +#endif + return CancellationToken.None; + } + + _cts.CancelAfter(Timeout); + if (_cts.IsCancellationRequested) { _cts.Dispose(); _cts = new CancellationTokenSource(Timeout); @@ -69,7 +80,17 @@ public CancellationToken Start(CancellationToken cancellationToken = default) /// Restart the timeout on the wrapped without reinitializing it, /// even if is already set to /// - 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() {