From 2201d9b3ef0895e6907dc6554e019ba35734085a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 9 Nov 2022 20:20:12 +0200 Subject: [PATCH 01/83] Bump version to 7.0.1 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6d7a7869ab..23802d5502 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.0 + 7.0.1 latest true enable From 2ad359bf0637b08fc3d9e215929367aa5fbb11e0 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 10 Nov 2022 13:29:46 +0200 Subject: [PATCH 02/83] Reset public API snapshots (#4749) (cherry picked from commit 14b505b3921624e662c91c57a3d040ffeb549e72) --- src/Npgsql.GeoJSON/PublicAPI.Shipped.txt | 2 +- src/Npgsql.Json.NET/PublicAPI.Shipped.txt | 2 +- .../PublicAPI.Unshipped.txt | 2 +- src/Npgsql/PublicAPI.Shipped.txt | 367 +++++------------- src/Npgsql/PublicAPI.Unshipped.txt | 366 ----------------- 5 files changed, 108 insertions(+), 631 deletions(-) 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/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/PublicAPI.Shipped.txt b/src/Npgsql/PublicAPI.Shipped.txt index 79818d3afd..6f09e92dd3 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 @@ -1829,12 +1763,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 +1796,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 +1836,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 +1856,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 +1900,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 +1909,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..ab058de62d 100644 --- a/src/Npgsql/PublicAPI.Unshipped.txt +++ b/src/Npgsql/PublicAPI.Unshipped.txt @@ -1,367 +1 @@ #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! From b98deafe3462a65be69ef0749097d3e8f4577e8f Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 12 Nov 2022 11:41:12 +0200 Subject: [PATCH 03/83] Fixes to DbDataSource (#4754) Fixes #4752 (cherry picked from commit 55ad6118300071b187bdc5d2840cae59925449de) --- src/Npgsql/NpgsqlDataSource.cs | 25 ++++++++++++------------- src/Npgsql/Shims/DbDataSource.cs | 2 +- test/Npgsql.Tests/DataSourceTests.cs | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 14 deletions(-) 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/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/test/Npgsql.Tests/DataSourceTests.cs b/test/Npgsql.Tests/DataSourceTests.cs index adbed90c0d..10565d4615 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,20 @@ 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)); + } } From b17850a6a55b6c225a1a7b9040fb9e732e056921 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 17 Nov 2022 09:32:39 +0100 Subject: [PATCH 04/83] Fix thread safety of NetTopologySuiteHandler (#4767) Fixes #4766 (cherry picked from commit d0b91b10ae2e31a290486ca19c90580df00c43af) --- .../Internal/NetTopologySuiteHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From 17c1fef11d33c6a8811126f8ef0c496f39477b16 Mon Sep 17 00:00:00 2001 From: jam40jeff Date: Fri, 18 Nov 2022 11:55:56 -0500 Subject: [PATCH 05/83] Update CommandCompleteMessage.cs (#4772) Fix ParseNumber to use ulong internally. (cherry picked from commit ae60e37ca2a14afcc3f912975dd1f71ad37d43ae) --- src/Npgsql/BackendMessages/CommandCompleteMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'; From 25578ea062e96830ef2da7892698d6873cedb1af Mon Sep 17 00:00:00 2001 From: Brar Piening Date: Thu, 17 Nov 2022 16:06:29 +0100 Subject: [PATCH 06/83] Set a valid activity span name in NpgsqlActivitySource.CommandStart (#4765) Fixes #4757 (cherry picked from commit 031a36a496744689615a2515d44b94d28da44b82) --- src/Npgsql/Internal/NpgsqlConnector.cs | 51 ++++++++++++++++----- src/Npgsql/KerberosUsernameProvider.cs | 61 ++++++++++++++------------ src/Npgsql/NpgsqlActivitySource.cs | 50 ++++++++++++++++++--- src/Npgsql/NpgsqlCommand.cs | 2 +- 4 files changed, 119 insertions(+), 45 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index cd1ef203bb..ca5d1b6eec 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; /// @@ -682,29 +693,47 @@ 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) 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/NpgsqlActivitySource.cs b/src/Npgsql/NpgsqlActivitySource.cs index 002cf4a638..ae6f46956d 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,19 +21,58 @@ static NpgsqlActivitySource() internal static bool IsEnabled => Source.HasListeners(); - internal static Activity? CommandStart(NpgsqlConnector connector, string sql) + internal static Activity? CommandStart(NpgsqlConnector connector, string commandText, CommandType commandType) { var settings = connector.Settings; - var activity = Source.StartActivity(settings.Database!, ActivityKind.Client); + + var dbName = settings.Database ?? connector.InferredUserName; + 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.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", dbName); + activity.SetTag("db.statement", commandText); activity.SetTag("db.connection_id", connector.Id); + if (dbOperation != null) + activity.SetTag("db.operation", dbOperation); + if (dbSqlTable != null) + activity.SetTag("db.sql.table", dbSqlTable); var endPoint = connector.ConnectedEndPoint; Debug.Assert(endPoint is not null); diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index e3d07c3ae9..c32a675b7f 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1609,7 +1609,7 @@ internal void TraceCommandStart(NpgsqlConnector connector) { Debug.Assert(CurrentActivity is null); if (NpgsqlActivitySource.IsEnabled) - CurrentActivity = NpgsqlActivitySource.CommandStart(connector, CommandText); + CurrentActivity = NpgsqlActivitySource.CommandStart(connector, CommandText, CommandType); } internal void TraceReceivedFirstResponse() From 28a132b6085d0292e0722ef87a8048573a8d9e44 Mon Sep 17 00:00:00 2001 From: Brar Piening Date: Sat, 3 Dec 2022 16:45:22 +0100 Subject: [PATCH 07/83] Remove package reference for System.Runtime.CompilerServices.Unsafe (#4797) This removes the package reference for System.Runtime.CompilerServices.Unsafe from TFM net7.0 and above. When testing, Npgsql actually built perfectly even when removing the package reference for all TFMs but this doesn't feel safe for now and also is not what is suggested by the documentation at: https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/7.0/unsafe-package (cherry picked from commit dd14e4f048e422a9da2012e1b79f294d2f6107de) --- src/Npgsql/Npgsql.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 @@ + + + + From 6c3dbd0f7cadc04ab9cd3755fc997615ca947115 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 25 Nov 2022 13:26:53 +0300 Subject: [PATCH 08/83] Fix linux postgresql ci (#4786) (cherry picked from commit 011e9a5921e0bc9250e7458e7723db641cb99dc3) --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe542161a2..d635b761d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 }} From 1317b980f030f4667bc48940707cbe970bb0bc5e Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 8 Dec 2022 14:21:28 +0300 Subject: [PATCH 09/83] Disable rich code navigations because of failures https://github.com/microsoft/RichCodeNavIndexer/issues/128 (cherry picked from commit b73f42e43a278d93a514ca09b1118119a614e88a) --- .github/workflows/rich-code-nav.yml | 1 + 1 file changed, 1 insertion(+) 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: From 7eb82abf2b8ab3f278ad485c1e675781b7d8ffcb Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sat, 17 Dec 2022 11:52:57 +0300 Subject: [PATCH 10/83] Fix deadlock from concurrent read and write failure (#4807) Fixes #4804 (cherry picked from commit 46adc6282931e2d6a16ef24e0817fa171c7fe8d0) --- src/Npgsql/NpgsqlDataReader.cs | 27 +++++++--- .../Util/ResettableCancellationTokenSource.cs | 54 ++++++++++++++++--- test/Npgsql.Tests/CommandTests.cs | 23 ++++++++ 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/Npgsql/NpgsqlDataReader.cs b/src/Npgsql/NpgsqlDataReader.cs index c6fc499720..e0ff2e021d 100644 --- a/src/Npgsql/NpgsqlDataReader.cs +++ b/src/Npgsql/NpgsqlDataReader.cs @@ -1160,17 +1160,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); + } } } 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/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index 4f24eb5e18..21e9da6085 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1384,6 +1384,29 @@ 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); + } + #region Logging [Test] From 7dd6211ce8a05ae80151218c458dfccde19f25ff Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 17 Dec 2022 16:26:23 +0100 Subject: [PATCH 11/83] Bump version to 7.0.2 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 23802d5502..54d82f9b2f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.1 + 7.0.2 latest true enable From 5d751a0cc10cf8f87f3042d6900ee9df38b583dc Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Wed, 21 Dec 2022 14:19:54 +0300 Subject: [PATCH 12/83] Fix reusing NpgsqlBatch with autoprepare (#4821) Fixes #4264 (cherry picked from commit 09a8387b4cf3597ff13e34eb4ac06e7b8c6a1d89) --- src/Npgsql/NpgsqlBatchCommand.cs | 16 ++++++++++++++- src/Npgsql/NpgsqlCommand.cs | 34 +++++++++++++++++++++++--------- src/Npgsql/NpgsqlDataReader.cs | 2 ++ test/Npgsql.Tests/BatchTests.cs | 24 ++++++++++++++++++++++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/Npgsql/NpgsqlBatchCommand.cs b/src/Npgsql/NpgsqlBatchCommand.cs index 78aedc1f7e..9e45f45c99 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,12 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg) OID = msg.OID; } + internal void ResetPreparation() + { + PreparedStatement = null; + ConnectorPreparedOn = null; + } + /// /// Returns the . /// diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index c32a675b7f..3550d821a2 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1346,20 +1346,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 +1390,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 diff --git a/src/Npgsql/NpgsqlDataReader.cs b/src/Npgsql/NpgsqlDataReader.cs index e0ff2e021d..498740c6c8 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(); } } diff --git a/test/Npgsql.Tests/BatchTests.cs b/test/Npgsql.Tests/BatchTests.cs index 342076714a..e59d3b9195 100644 --- a/test/Npgsql.Tests/BatchTests.cs +++ b/test/Npgsql.Tests/BatchTests.cs @@ -731,6 +731,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 From 44ec27bb3de4d51064d5ae48af2dff03abaf0bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Andr=C3=A9?= <2341261+manandre@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:04:22 +0100 Subject: [PATCH 13/83] Fix header size for GeoJSON collections (#4828) Fixes #4827 (cherry picked from commit 2c81aed1419574152cf92fc091a58985f9c135d4) --- src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs | 14 ++--- test/Npgsql.PluginTests/GeoJSONTests.cs | 61 +++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs b/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs index 4c3c90b866..4db9ffcc12 100644 --- a/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs +++ b/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs @@ -449,7 +449,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 +476,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) { @@ -510,7 +510,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 +537,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 +564,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 +590,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 +717,4 @@ enum EwkbGeometryType : uint HasSrid = 0x20000000, HasM = 0x40000000, HasZ = 0x80000000 -} \ No newline at end of file +} diff --git a/test/Npgsql.PluginTests/GeoJSONTests.cs b/test/Npgsql.PluginTests/GeoJSONTests.cs index 2f44d0ec18..688295eee0 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,66 @@ 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)); + } + ValueTask OpenConnectionAsync(GeoJSONOptions options = GeoJSONOptions.None) => GetDataSource(options).OpenConnectionAsync(); From 1984f79c1da52f73c69ee5cd312408343e1e3bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Andr=C3=A9?= <2341261+manandre@users.noreply.github.com> Date: Thu, 22 Dec 2022 09:32:33 +0100 Subject: [PATCH 14/83] Ensure required bytes when reading Polygon types (#4831) (cherry picked from commit 68a63a8319045a9ff3d36156515c1e93c5ab898c) --- src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs | 4 +- test/Npgsql.PluginTests/GeoJSONTests.cs | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs b/src/Npgsql.GeoJSON/Internal/GeoJSONHandler.cs index 4db9ffcc12..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) { @@ -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); diff --git a/test/Npgsql.PluginTests/GeoJSONTests.cs b/test/Npgsql.PluginTests/GeoJSONTests.cs index 688295eee0..0630eebc8d 100644 --- a/test/Npgsql.PluginTests/GeoJSONTests.cs +++ b/test/Npgsql.PluginTests/GeoJSONTests.cs @@ -346,6 +346,68 @@ public async Task Import_big_geometry() 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(); From 6d1e0997431d4bf8f861245956cfe59d5e44a03e Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 9 Jan 2023 21:24:35 +0100 Subject: [PATCH 15/83] Fix DateTime truncation when writing as date (#4863) Fixes #4861 (cherry picked from commit 957c8dea8cdb09a5264174556d37af3f15df8f80) --- .../Internal/TypeHandlers/DateTimeHandlers/DateHandler.cs | 2 +- test/Npgsql.Tests/Types/DateTimeTests.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) 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/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() From 6b43c83cdf1fd8a9fb665c87eeec584e5820fa8b Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sat, 28 Jan 2023 21:47:59 +0300 Subject: [PATCH 16/83] Fix cancellation being able to cancel prepended queries (#4907) Fixes #4906 (cherry picked from commit 218a160e396636f2c56ca7215d264ce644b1464f) --- src/Npgsql/Internal/NpgsqlConnector.cs | 250 ++++++++++++++++--------- src/Npgsql/NpgsqlCommand.cs | 4 +- test/Npgsql.Tests/CommandTests.cs | 74 ++++++++ 3 files changed, 232 insertions(+), 96 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index ca5d1b6eec..24c4809eaa 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -147,6 +147,13 @@ internal string InferredUserName /// 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; } @@ -218,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; @@ -353,8 +373,6 @@ 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); @@ -512,7 +530,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); @@ -1293,9 +1311,14 @@ 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(); } - 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); } } @@ -1662,18 +1685,39 @@ static RemoteCertificateValidationCallback SslRootValidation(string certRootPath #region Cancel + 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 PerformUserCancellation() { var connection = Connection; if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader) 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); + } + + // Wait before we've read all responses for the prepended queries + // as we can't gracefully handle their cancellation. + // Break makes sure that it's going to be set even if we fail while reading them. + ReadingPrependedMessagesMRE.Wait(); try { @@ -1686,28 +1730,18 @@ internal void PerformUserCancellation() { if (cancellationTimeout > 0) { - lock (this) - { - if (!IsConnected) - return; - UserTimeout = cancellationTimeout; - ReadBuffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout); - ReadBuffer.Cts.CancelAfter(cancellationTimeout); - } + UserTimeout = cancellationTimeout; + ReadBuffer.Timeout = TimeSpan.FromMilliseconds(cancellationTimeout); + ReadBuffer.Cts.CancelAfter(cancellationTimeout); } return; } } - lock (this) - { - if (!IsConnected) - return; - UserTimeout = -1; - ReadBuffer.Timeout = _cancelImmediatelyTimeout; - ReadBuffer.Cts.Cancel(); - } + UserTimeout = -1; + ReadBuffer.Timeout = _cancelImmediatelyTimeout; + ReadBuffer.Cts.Cancel(); } finally { @@ -1782,8 +1816,7 @@ void DoCancelRequest(int backendProcessId, int backendSecretKey) } finally { - lock (this) - FullCleanup(); + FullCleanup(); } } @@ -1889,7 +1922,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) { @@ -1918,9 +1951,10 @@ internal void Close() } State = ConnectorState.Closed; - FullCleanup(); - LogMessages.ClosedPhysicalConnection(ConnectionLogger, Host, Port, Database, UserFacingConnectionString, Id); } + + FullCleanup(); + LogMessages.ClosedPhysicalConnection(ConnectionLogger, Host, Port, Database, UserFacingConnectionString, Id); } internal bool TryRemovePendingEnlistedConnector(Transaction transaction) @@ -1949,23 +1983,24 @@ internal Exception Break(Exception reason) { Debug.Assert(!IsClosed); - // See PerformUserCancellation on why we take CancelLock - lock (CancelLock) - lock (this) + Monitor.Enter(SyncObj); + + if (State == ConnectorState.Broken) { - if (State == ConnectorState.Broken) - return reason; + // 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; + } - // 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(); - } + try + { + // If we're broken while reading prepended messages + // the cancellation request might still be waiting on the MRE. + // Unblock it. + ReadingPrependedMessagesMRE.Set(); LogMessages.BreakingConnection(ConnectionLogger, Id, reason); @@ -1973,62 +2008,94 @@ internal Exception Break(Exception reason) // 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(); } } @@ -2272,7 +2339,7 @@ internal UserAction StartUserAction( if (!_isKeepAliveEnabled) return DoStartUserAction(newState, command); - lock (this) + lock (SyncObj) { if (!IsConnected) { @@ -2349,7 +2416,7 @@ internal void EndUserAction() if (_isKeepAliveEnabled) { - lock (this) + lock (SyncObj) { if (IsReady || !IsConnected) return; @@ -2391,10 +2458,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 @@ -2427,7 +2491,7 @@ void PerformKeepAlive(object? state) } finally { - Monitor.Exit(this); + Monitor.Exit(SyncObj); } } #pragma warning restore CA1801 // Review unused parameters diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 3550d821a2..06140043bd 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1431,9 +1431,7 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior TraceCommandStart(connector); // If a cancellation is in progress, wait for it to "complete" before proceeding (#615) - lock (connector.CancelLock) - { - } + connector.ResetCancellation(); // 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 diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index 21e9da6085..022af1c09e 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1407,6 +1407,80 @@ public async Task Concurrent_read_write_failure_deadlock() 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)")] + 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 + }; + await using var dataSource = CreateDataSource(csb.ConnectionString); + await using var conn = await dataSource.OpenConnectionAsync(); + // 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; + } + #region Logging [Test] From 75d88a3eb87e685eb2b60658773b40081bbe9316 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sat, 28 Jan 2023 22:00:23 +0300 Subject: [PATCH 17/83] Fix test for 6b43c83 --- test/Npgsql.Tests/CommandTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index 022af1c09e..3c3d98aad4 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1419,8 +1419,8 @@ public async Task Not_cancel_prepended_query([Values] bool failPrependedQuery) { NoResetOnClose = false }; - await using var dataSource = CreateDataSource(csb.ConnectionString); - await using var conn = await dataSource.OpenConnectionAsync(); + 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(); From a7de5c9bce54d2886820e53e4ababe632a173fca Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 31 Jan 2023 23:29:47 +0300 Subject: [PATCH 18/83] Fix not checking for oversize buffer while loading backend types (#4912) Fixes #4911 (cherry picked from commit 029ec4a10a3daaff34b70bdc5f593c7e6f42dad5) --- src/Npgsql/PostgresDatabaseInfo.cs | 37 +++++++++++++------------- test/Npgsql.Tests/NotificationTests.cs | 16 +++++++++++ 2 files changed, 35 insertions(+), 18 deletions(-) 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/test/Npgsql.Tests/NotificationTests.cs b/test/Npgsql.Tests/NotificationTests.cs index 0092dfdad4..b76b5219a0 100644 --- a/test/Npgsql.Tests/NotificationTests.cs +++ b/test/Npgsql.Tests/NotificationTests.cs @@ -212,4 +212,20 @@ 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 + await using var dataSource = CreateDataSource(csb => csb.ReadBufferSize = 4096); + await using var conn = await dataSource.OpenConnectionAsync(); + + 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(); + } } From 162188719c32ba76b68d3a45a1d9a551f479d1a5 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 31 Jan 2023 23:50:43 +0300 Subject: [PATCH 19/83] Fix test for a7de5c9 --- test/Npgsql.Tests/NotificationTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/Npgsql.Tests/NotificationTests.cs b/test/Npgsql.Tests/NotificationTests.cs index b76b5219a0..09b56b0e44 100644 --- a/test/Npgsql.Tests/NotificationTests.cs +++ b/test/Npgsql.Tests/NotificationTests.cs @@ -218,8 +218,12 @@ public async Task Big_notice_while_loading_types() { await using var adminConn = await OpenConnectionAsync(); // Max notification payload is 8000 - await using var dataSource = CreateDataSource(csb => csb.ReadBufferSize = 4096); - await using var conn = await dataSource.OpenConnectionAsync(); + 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}"); From babc9c76c3217f26e60dc47b54d9cd3387550e7e Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 10 Feb 2023 18:12:26 +0300 Subject: [PATCH 20/83] Fix write buffer's WriteSpaceLeft check while closing prepared statement (#4921) Fixes #4920 (cherry picked from commit 7e512e52c043ec22a73bf4d5a22c69647784ac9f) --- .../Internal/NpgsqlConnector.FrontendMessages.cs | 2 +- test/Npgsql.Tests/PrepareTests.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs b/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs index c61f7b48bd..c38f39575a 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs @@ -251,7 +251,7 @@ 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) + if (WriteBuffer.WriteSpaceLeft < len) return FlushAndWrite(len, type, name, async, cancellationToken); Write(len, type, name); diff --git a/test/Npgsql.Tests/PrepareTests.cs b/test/Npgsql.Tests/PrepareTests.cs index 12f8d2e3b5..bb539e6e04 100644 --- a/test/Npgsql.Tests/PrepareTests.cs +++ b/test/Npgsql.Tests/PrepareTests.cs @@ -777,6 +777,22 @@ public async Task Explicitly_prepared_statement_invalidation() 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(); + } + NpgsqlConnection OpenConnectionAndUnprepare(string? connectionString = null) { var conn = OpenConnection(connectionString); From 7a5c4492de1546cae7eadf5e34b3b89604eada1b Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 10 Feb 2023 22:12:20 +0300 Subject: [PATCH 21/83] Fix executing connectionless command on NpgsqlDataSource with multiplexing (#4841) Fixes #4840 (cherry picked from commit ad7dee8efa2afd25fc64a8e8bea8da7ca3b13ad8) --- src/Npgsql/MultiplexingDataSource.cs | 2 +- test/Npgsql.Tests/DataSourceTests.cs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Npgsql/MultiplexingDataSource.cs b/src/Npgsql/MultiplexingDataSource.cs index 2eb1763c3c..137b60832a 100644 --- a/src/Npgsql/MultiplexingDataSource.cs +++ b/src/Npgsql/MultiplexingDataSource.cs @@ -98,7 +98,7 @@ async Task MultiplexingWriteLoop() } connector = await OpenNewConnector( - command.Connection!, + command.InternalConnection!, new NpgsqlTimeout(TimeSpan.FromSeconds(Settings.Timeout)), async: true, CancellationToken.None); diff --git a/test/Npgsql.Tests/DataSourceTests.cs b/test/Npgsql.Tests/DataSourceTests.cs index 10565d4615..7ad117c362 100644 --- a/test/Npgsql.Tests/DataSourceTests.cs +++ b/test/Npgsql.Tests/DataSourceTests.cs @@ -260,4 +260,24 @@ public async Task As_DbDataSource([Values] bool 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)); + } } From 1335dd2a8a238dc609841f1c359b2b56e3099cf1 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 10 Feb 2023 22:51:05 +0300 Subject: [PATCH 22/83] Fi Unique_constraint test for 7.0.2 --- test/Npgsql.Tests/SchemaTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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] From 0d3e8ad666aedc480c5dc8d9afeaae555cd7e4a3 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 15 Feb 2023 11:27:59 +0100 Subject: [PATCH 23/83] Bump version to 7.0.4 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 54d82f9b2f..8b2c7fe04c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.2 + 7.0.4 latest true enable From aed225ec1abe2a3bd9acd0a69a878f2d2bdd9ba6 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sat, 18 Feb 2023 13:41:12 +0300 Subject: [PATCH 24/83] Replace --output with --property for pack in ci to fix 7.0.2 breaking change (#4938) https://github.com/dotnet/sdk/issues/30625#issuecomment-1433569096 (cherry picked from commit 09fa2a218d9ccc467d47a80348bbf03aa9cb0ccf) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d635b761d7..bd2750f79a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -351,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 From 594064375a4805211b6a1ec1c389d7cd44827aaf Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 2 Mar 2023 16:06:24 +0300 Subject: [PATCH 25/83] Fix closing unpooled connection with transaction scope (#4964) Fixes #4963 (cherry picked from commit bceb9e134c36786d348f833cfc3b1a64cf77cbc4) --- src/Npgsql/NpgsqlConnection.cs | 7 ++--- src/Npgsql/UnpooledDataSource.cs | 9 ------ .../DistributedTransactionTests.cs | 31 ------------------- test/Npgsql.Tests/SystemTransactionTests.cs | 23 ++++++++++++++ 4 files changed, 25 insertions(+), 45 deletions(-) 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/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/test/Npgsql.Tests/DistributedTransactionTests.cs b/test/Npgsql.Tests/DistributedTransactionTests.cs index 856861fa3a..72b886624e 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) diff --git a/test/Npgsql.Tests/SystemTransactionTests.cs b/test/Npgsql.Tests/SystemTransactionTests.cs index 27a9d057e1..44abc2b24d 100644 --- a/test/Npgsql.Tests/SystemTransactionTests.cs +++ b/test/Npgsql.Tests/SystemTransactionTests.cs @@ -293,6 +293,29 @@ public void Single_unpooled_connection() scope.Complete(); } + [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4963")] + public void Single_unpooled_closed_connection() + { + var csb = new NpgsqlConnectionStringBuilder(ConnectionString) + { + Pooling = false, + Enlist = true + }; + 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(dataSource.Statistics.Total, Is.EqualTo(1)); + scope.Complete(); + } + + Assert.That(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) From e9b6ea4beaaf6dde97a42971558d641e881eb474 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 3 Mar 2023 21:08:15 +0100 Subject: [PATCH 26/83] Fix release nuget pack in CI (cherry picked from commit eabbc8a1f6bad1ec0633c893ea0905799b3f52be) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd2750f79a..644d12f066 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -385,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 From bb0623e096f9e054d9341c6b125941109ae53ba4 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 4 Mar 2023 15:52:37 +0100 Subject: [PATCH 27/83] Dispose X509Certificate2 when the connector is cleaned up (#4975) Fixes #4969 (cherry picked from commit caa6b2a883292fb0a27f3c9537c31e36032716a9) --- src/Npgsql/Internal/NpgsqlConnector.cs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index 24c4809eaa..0bd9024e51 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -282,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; } @@ -756,7 +758,6 @@ async ValueTask GetUsernameAsyncInternal() async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken, bool isFirstAttempt = true) { - var cert = default(X509Certificate2?); try { if (async) @@ -815,23 +816,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); @@ -846,7 +847,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; } @@ -912,7 +913,8 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat } catch { - cert?.Dispose(); + _certificate?.Dispose(); + _certificate = null; _stream?.Dispose(); _stream = null!; @@ -2160,6 +2162,12 @@ void Cleanup() Connection = null; PostgresParameters.Clear(); _currentCommand = null; + + if (_certificate is not null) + { + _certificate.Dispose(); + _certificate = null; + } } void GenerateResetMessage() From 7c193445d4ceab64660c97b5e03fe38c16447a29 Mon Sep 17 00:00:00 2001 From: Jon Roberts Date: Tue, 7 Mar 2023 12:34:54 -0600 Subject: [PATCH 28/83] Add SupportsSavepoints property to NpgsqlTransaction (#4978) (cherry picked from commit c2ecdbb5457c16c2b102b57867ba845d85a45b39) --- Npgsql.sln.DotSettings | 1 + src/Npgsql/NpgsqlTransaction.cs | 14 +++++++++++++- src/Npgsql/PublicAPI.Shipped.txt | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) 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/NpgsqlTransaction.cs b/src/Npgsql/NpgsqlTransaction.cs index 0f0cb20fc6..9e21f7478c 100644 --- a/src/Npgsql/NpgsqlTransaction.cs +++ b/src/Npgsql/NpgsqlTransaction.cs @@ -356,6 +356,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 +512,4 @@ internal void UnbindIfNecessary() } #endregion -} \ No newline at end of file +} diff --git a/src/Npgsql/PublicAPI.Shipped.txt b/src/Npgsql/PublicAPI.Shipped.txt index 6f09e92dd3..87a3c12c5d 100644 --- a/src/Npgsql/PublicAPI.Shipped.txt +++ b/src/Npgsql/PublicAPI.Shipped.txt @@ -1741,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! From 5e0141f02484095fb81e45217f199a36e2df4657 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 18 Mar 2023 17:27:36 +0100 Subject: [PATCH 29/83] Clone connection string builder when building data source (#5000) (cherry picked from commit 1566d76ea941f0268b07dab29fd5d8e0e71a9e1f) --- src/Npgsql/NpgsqlDataSourceBuilder.cs | 11 ++++++----- test/Npgsql.Tests/DataSourceTests.cs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) 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/test/Npgsql.Tests/DataSourceTests.cs b/test/Npgsql.Tests/DataSourceTests.cs index 7ad117c362..5c693a6eab 100644 --- a/test/Npgsql.Tests/DataSourceTests.cs +++ b/test/Npgsql.Tests/DataSourceTests.cs @@ -280,4 +280,17 @@ public async Task Multiplexing_connectionless_command_open_connection() 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")); + } } From ca433996dd7f6d63fba77e8a3965a28c13449dfb Mon Sep 17 00:00:00 2001 From: yoshihikoueno <38683757+yoshihikoueno@users.noreply.github.com> Date: Tue, 21 Mar 2023 20:20:48 +0900 Subject: [PATCH 30/83] Check the message after sending a PasswordMessage (#5006) (cherry picked from commit fbc7538b268f4827d021e50092b5f1ddc5b72f49) --- src/Npgsql/Internal/NpgsqlConnector.Auth.cs | 53 ++++++++++----------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs index 02a8890dde..ff86274a30 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs @@ -19,35 +19,38 @@ 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})"); + } } } @@ -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) @@ -204,10 +206,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() { @@ -281,7 +279,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 From 597c481d6697a66d0cfcec6a94dfd183c4867b26 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 29 Mar 2023 00:20:14 +0300 Subject: [PATCH 31/83] Correct docs on DI lifetime (#5020) (cherry picked from commit b28ea2e8b99c2b7f1dd9d9a49904c7287ccdc58c) --- .../NpgsqlServiceCollectionExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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. From acd3d5e93404f749e647a1a0435d747980604f55 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 7 Apr 2023 16:36:06 +0300 Subject: [PATCH 32/83] Fix a possible timeout with gss/sspi auth (#5028) Fixes #4888 (cherry picked from commit ae3309f259db536be5ca16a327550f2d654b670c) --- src/Npgsql/Internal/NpgsqlConnector.Auth.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs index ff86274a30..842befed88 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs @@ -299,13 +299,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); } From 72bef2b46a53973be879c30b3ca582b595c0c46b Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 23 Apr 2023 22:50:23 +0300 Subject: [PATCH 33/83] Fix deadlock while cancelling a query (#5033) Fixes #5032 (cherry picked from commit 276bb7db351ed785e2ea3b74cce85d8a3ca49fff) --- src/Npgsql/Internal/NpgsqlConnector.cs | 16 ++++++++++------ test/Npgsql.Tests/CommandTests.cs | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index 0bd9024e51..1b2aed9bb0 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -1701,7 +1701,7 @@ internal void ResetCancellation() internal void PerformUserCancellation() { var connection = Connection; - if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader) + if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader || UserCancellationRequested) return; // Take the lock first to make sure there is no concurrent Break. @@ -1716,13 +1716,17 @@ internal void PerformUserCancellation() Monitor.Enter(CancelLock); } - // Wait before we've read all responses for the prepended queries - // as we can't gracefully handle their cancellation. - // Break makes sure that it's going to be set even if we fail while reading them. - ReadingPrependedMessagesMRE.Wait(); - try { + // Wait before we've read all responses for the prepended queries + // as we can't gracefully handle their cancellation. + // Break makes sure that it's going to be set even if we fail while reading them. + + // We don't wait indefinitely to avoid deadlocks from synchronous CancellationToken.Register + // See #5032 + if (!ReadingPrependedMessagesMRE.Wait(0)) + return; + _userCancellationRequested = true; if (AttemptPostgresCancellation && SupportsPostgresCancellation) diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index 3c3d98aad4..c268cef8e3 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -319,6 +319,7 @@ public async Task Cancel_async_immediately() } [Test, Description("Cancels an async query with the cancellation token, with successful PG cancellation")] + [Explicit("Flaky due to #5033")] public async Task Cancel_async_soft() { if (IsMultiplexing) @@ -1409,6 +1410,7 @@ public async Task Concurrent_read_write_failure_deadlock() [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) From faf3268870bb5c2a5cd61274f3f8c2dc561f3b6a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 24 Apr 2023 16:15:56 +0200 Subject: [PATCH 34/83] Bump version to 7.0.6 --- Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8b2c7fe04c..70a3668f8d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.4 + 7.0.6 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 From 10a23e26da06fdb060849b1811293d3a452d4292 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 12 May 2023 12:32:19 +0300 Subject: [PATCH 35/83] FIx multiple hosts with disabled sql rewriting (#5056) Fixes #5055 (cherry picked from commit e4c0d3f49f65ca5a8a8678b012ab5a79beaf829d) --- src/Npgsql/Internal/NpgsqlConnector.cs | 14 +++++++++++--- test/Npgsql.Tests/MultipleHostsTests.cs | 21 ++++++++++++++++++++- test/Npgsql.Tests/TestUtil.cs | 3 +++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index 1b2aed9bb0..dad17aa682 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -637,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) @@ -2641,6 +2643,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/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/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 From 8acf7cd23de306b39e0d754111dd88d4bad317d0 Mon Sep 17 00:00:00 2001 From: Rafi Shamim Date: Tue, 16 May 2023 03:56:53 -0400 Subject: [PATCH 36/83] Treat pg_users.user_sysid as uint rather than int (#5057) (cherry picked from commit 60c4f5241b1371123b2cfde28b35e6c6e42f1861) --- src/Npgsql/NpgsqlSchema.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); From 9ef9f27b8bc980b2bb20e253751e73d3fcc67b71 Mon Sep 17 00:00:00 2001 From: Brar Piening Date: Sun, 11 Jun 2023 20:20:12 +0200 Subject: [PATCH 37/83] Always parse the timeline id as uint (#5102) This is required to support PostgreSQL 16+ --- .../Replication/ReplicationConnection.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Npgsql/Replication/ReplicationConnection.cs b/src/Npgsql/Replication/ReplicationConnection.cs index 6a09c13811..4c185d0985 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), @@ -824,22 +824,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 +946,4 @@ internal void CheckDisposed() if (_isDisposed) throw new ObjectDisposedException(GetType().Name); } -} \ No newline at end of file +} From d7e9bb8b3ab28b62d3ac269049db418360a1c161 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 19 Jun 2023 13:51:05 +0200 Subject: [PATCH 38/83] Add empty constructors to Npgsql{Path,Polygon} (#5114) (cherry picked from commit 1b8bb7e55971e30b4d8f6fd41287c2bdfd31cee3) --- src/Npgsql/NpgsqlTypes/NpgsqlTypes.cs | 10 ++++++++-- test/Npgsql.Tests/Types/GeometricTypeTests.cs | 2 +- test/Npgsql.Tests/TypesTests.cs | 8 ++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) 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/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() { From 4e76cbf970ba040ca11c96055fb6b77b581a7922 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Mon, 19 Jun 2023 15:03:37 +0300 Subject: [PATCH 39/83] Fix reading a single char with TextHandler (#5111) Fixes #5110 (cherry picked from commit 9feab3ccd9a3a21049b8af38f3ae46fabbd5de98) --- .../Internal/TypeHandlers/TextHandler.cs | 22 ++++++++++++------- test/Npgsql.Tests/CommandTests.cs | 1 + test/Npgsql.Tests/CopyTests.cs | 19 ++++++++++++++++ test/Npgsql.Tests/ReaderTests.cs | 18 +++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) 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/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index c268cef8e3..251a685c5b 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -280,6 +280,7 @@ public async Task Prepare_timeout_hard([Values] SyncOrAsync async) #region Cancel [Test, Description("Basic cancellation scenario")] + [Ignore("Flaky, see https://github.com/npgsql/npgsql/issues/5070")] public async Task Cancel() { if (IsMultiplexing) diff --git a/test/Npgsql.Tests/CopyTests.cs b/test/Npgsql.Tests/CopyTests.cs index adac0916f4..7935aabf6e 100644 --- a/test/Npgsql.Tests/CopyTests.cs +++ b/test/Npgsql.Tests/CopyTests.cs @@ -778,6 +778,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 diff --git a/test/Npgsql.Tests/ReaderTests.cs b/test/Npgsql.Tests/ReaderTests.cs index 5dc6bd6534..2d92683a28 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] From 60eeb0b42d3c2a2b6917652d25b9cbbabfb03c69 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 25 Aug 2023 18:11:21 +0300 Subject: [PATCH 40/83] Fix seek for ColumnStream (#5224) Fixes #5223 (cherry picked from commit 820cfcddf95dd7736affa53fafd4f4e04b473e04) --- .../Internal/NpgsqlReadBuffer.Stream.cs | 19 ++++---- test/Npgsql.Tests/ReaderTests.cs | 44 +++++++++++++++++++ 2 files changed, 55 insertions(+), 8 deletions(-) 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/test/Npgsql.Tests/ReaderTests.cs b/test/Npgsql.Tests/ReaderTests.cs index 2d92683a28..ebd3a89c13 100644 --- a/test/Npgsql.Tests/ReaderTests.cs +++ b/test/Npgsql.Tests/ReaderTests.cs @@ -1471,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 From f60a04e80c701fbc029c700f35d21b0e947cb31e Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 25 Aug 2023 18:10:29 +0300 Subject: [PATCH 41/83] Fix potential lost messages/protocol desync after resetting oversize buffer (#5219) Fixes #5218 (cherry picked from commit 9ce3ba0a35255cc1a419f0096d51ba0eb48a8f60) # Conflicts: # test/Npgsql.Tests/CommandTests.cs --- src/Npgsql/Internal/NpgsqlConnector.cs | 14 +++++++ test/Npgsql.Tests/CommandTests.cs | 56 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index dad17aa682..25262b323a 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -2293,6 +2293,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; diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index 251a685c5b..e2482446b7 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1484,6 +1484,62 @@ await server 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] From 29d9395bca7d43a957206fb12e52e41f8095ec2d Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 25 Aug 2023 18:12:30 +0300 Subject: [PATCH 42/83] Fix possible nre while writing to RawCopyStream after break (#5213) Fixes #5209 (cherry picked from commit d8c70e561740b701909d6aa08d069452e971f864) # Conflicts: # test/Npgsql.Tests/CopyTests.cs --- src/Npgsql/NpgsqlRawCopyStream.cs | 50 ++++++++--------------- test/Npgsql.Tests/CopyTests.cs | 24 +++++++++++ test/Npgsql.Tests/Support/PgServerMock.cs | 6 +-- 3 files changed, 44 insertions(+), 36 deletions(-) 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/test/Npgsql.Tests/CopyTests.cs b/test/Npgsql.Tests/CopyTests.cs index 7935aabf6e..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; @@ -1115,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/Support/PgServerMock.cs b/test/Npgsql.Tests/Support/PgServerMock.cs index 639124be5c..6a83cc0248 100644 --- a/test/Npgsql.Tests/Support/PgServerMock.cs +++ b/test/Npgsql.Tests/Support/PgServerMock.cs @@ -328,12 +328,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 +381,4 @@ public void Dispose() _disposed = true; } -} \ No newline at end of file +} From 8060929d072effedcbb6250c32ecde22a9955c5a Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Fri, 25 Aug 2023 18:35:44 +0300 Subject: [PATCH 43/83] Fix possible protocol desync with SchemaOnly and auto prepare (#5221) Fixes #5220 (cherry picked from commit 8c0e93b32fbffbb7802cf31a925195c2128629a5) --- src/Npgsql/NpgsqlDataReader.cs | 15 ++++++++------- test/Npgsql.Tests/AutoPrepareTests.cs | 5 ++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Npgsql/NpgsqlDataReader.cs b/src/Npgsql/NpgsqlDataReader.cs index 498740c6c8..c44c56410a 100644 --- a/src/Npgsql/NpgsqlDataReader.cs +++ b/src/Npgsql/NpgsqlDataReader.cs @@ -728,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 @@ -735,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; } diff --git a/test/Npgsql.Tests/AutoPrepareTests.cs b/test/Npgsql.Tests/AutoPrepareTests.cs index c81affc542..d3aff1c07d 100644 --- a/test/Npgsql.Tests/AutoPrepareTests.cs +++ b/test/Npgsql.Tests/AutoPrepareTests.cs @@ -542,7 +542,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 +559,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] From 55c644e136cc6e5250334701ece78d664804726b Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sat, 26 Aug 2023 11:11:27 +0300 Subject: [PATCH 44/83] Fix possible race condition with unprepare (#5228) Fixes #5227 (cherry picked from commit 0ac21a30cb286931bac5503d15a720120bef4f9b) --- src/Npgsql/NpgsqlCommand.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 06140043bd..44cb8887c6 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -786,9 +786,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 +806,6 @@ async Task Unprepare(bool async, CancellationToken cancellationToken = default) } Expect(await connector.ReadMessage(async), connector); - - if (async) - await sendTask; - else - sendTask.GetAwaiter().GetResult(); } } @@ -1134,14 +1128,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); @@ -1357,7 +1348,7 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior ResetPreparation(); goto case false; } - + batchCommand.Parameters.ProcessParameters(dataSource.TypeMapper, validateParameterValues, CommandType); } } From 3b57938420e5ca477d61c7c08d570b7d0ed7afda Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 27 Aug 2023 13:37:37 +0300 Subject: [PATCH 45/83] Fix DeriveParametersForQuery not waiting for sendTask to complete (#5230) Fixes #5229 (cherry picked from commit 255c5ba5bb8d7e2d1211d5ff90c25601585f7ffa) --- src/Npgsql/NpgsqlCommand.cs | 179 +++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 76 deletions(-) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 44cb8887c6..743187bdca 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -545,58 +545,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]; - 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."); + var (npgsqlDbType, postgresType) = connector.TypeMapper.GetTypeInfoByOid(paramOid); - param.DataTypeName = postgresType.DisplayName; - param.PostgresType = postgresType; - if (npgsqlDbType.HasValue) - param.NpgsqlDbType = npgsqlDbType.Value; + 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; + } + 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(); } } @@ -681,53 +695,66 @@ 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!; - if (pStatement.StatementBeingReplaced != null) - { - Expect(await connector.ReadMessage(async), connector); - pStatement.StatementBeingReplaced.CompleteUnprepare(); - pStatement.StatementBeingReplaced = null; + if (pStatement.StatementBeingReplaced != null) + { + Expect(await connector.ReadMessage(async), connector); + pStatement.StatementBeingReplaced.CompleteUnprepare(); + 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); From 87488b7dbde50bb426615b1db2bd90c3ca4d4dda Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 29 Aug 2023 08:43:05 +0300 Subject: [PATCH 46/83] Make CreateBatchCommand return NpgsqlBatchCommand (#5241) Closes #5238 (cherry picked from commit 20d0b6c8a82b4d4d5d948b82e517bd05dcc742c4) --- src/Npgsql/NpgsqlBatch.cs | 7 +++++-- src/Npgsql/PublicAPI.Unshipped.txt | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) 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/PublicAPI.Unshipped.txt b/src/Npgsql/PublicAPI.Unshipped.txt index ab058de62d..38c7b7978c 100644 --- a/src/Npgsql/PublicAPI.Unshipped.txt +++ b/src/Npgsql/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Npgsql.NpgsqlBatch.CreateBatchCommand() -> Npgsql.NpgsqlBatchCommand! From b74afa7b5346772c819c39df5dade0436a37e3df Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 14 Sep 2023 13:51:03 +0300 Subject: [PATCH 47/83] =?UTF-8?q?Fix=20completing=20TransactionScope=20wit?= =?UTF-8?q?h=20distributed=20transaction=20and=20undi=E2=80=A6=20(#5260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #5246 (cherry picked from commit 2d2245327faf3d39d570f2372a86bf9eadf0c8dd) --- src/Npgsql/VolatileResourceManager.cs | 8 ++++++-- .../Npgsql.Tests/DistributedTransactionTests.cs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Npgsql/VolatileResourceManager.cs b/src/Npgsql/VolatileResourceManager.cs index 84c28868e3..70afea0557 100644 --- a/src/Npgsql/VolatileResourceManager.cs +++ b/src/Npgsql/VolatileResourceManager.cs @@ -121,7 +121,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!; @@ -301,4 +305,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.Tests/DistributedTransactionTests.cs b/test/Npgsql.Tests/DistributedTransactionTests.cs index 72b886624e..0a45be8954 100644 --- a/test/Npgsql.Tests/DistributedTransactionTests.cs +++ b/test/Npgsql.Tests/DistributedTransactionTests.cs @@ -364,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; From eff6b18aa14cdd329b14c5168781f61bbafec3a0 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 14 Sep 2023 18:44:53 +0300 Subject: [PATCH 48/83] Test against PG16 in CI (#5262) (cherry picked from commit fcebb3a6d9f153c043184d5a9f63d183b42ac927) --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 644d12f066..049e8fd91f 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, 12, 11] 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 From 005e2367172ea1153bf3007498b6b76fa425a472 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 14 Sep 2023 18:25:27 +0200 Subject: [PATCH 49/83] Bump version to 7.0.7 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 70a3668f8d..da195c1000 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.6 + 7.0.7 latest true enable From 3390cdf0f6d043645fb04b8c427e6c35e7e6b845 Mon Sep 17 00:00:00 2001 From: Erik Desjardins <59450623+erikdesj@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:34:37 -0400 Subject: [PATCH 50/83] PruneIdleConnectors: consider broken and lifetime-exceeded connections as pruned (#5184) Fixes #5180 (cherry picked from commit 4ffbff1ed89088912662dc0ae45b7a4ab355d405) --- src/Npgsql/PoolingDataSource.cs | 5 ++-- test/Npgsql.Tests/PoolTests.cs | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/Npgsql/PoolingDataSource.cs b/src/Npgsql/PoolingDataSource.cs index f6a87c9e9b..5bdcadad69 100644 --- a/src/Npgsql/PoolingDataSource.cs +++ b/src/Npgsql/PoolingDataSource.cs @@ -439,10 +439,9 @@ static void PruneIdleConnectors(object? state) connector != null) { if (pool.CheckIdleConnector(connector)) - { pool.CloseConnector(connector); - toPrune--; - } + + toPrune--; } } 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() { From 21c41cc1d70d6acce44789cbcf085e69ad2ea6e4 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Wed, 27 Sep 2023 19:39:21 +0300 Subject: [PATCH 51/83] Fix setting IsReadOnly with old readers's schema (#5286) Fixes #5284 (cherry picked from commit aa32e4f479a37a71a965edb52cc4fd837895c1fb) --- src/Npgsql/NpgsqlDataReader.cs | 1 + test/Npgsql.Tests/ReaderOldSchemaTests.cs | 53 ++++++++--------------- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/src/Npgsql/NpgsqlDataReader.cs b/src/Npgsql/NpgsqlDataReader.cs index c44c56410a..b5d37c37c3 100644 --- a/src/Npgsql/NpgsqlDataReader.cs +++ b/src/Npgsql/NpgsqlDataReader.cs @@ -2169,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/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")] From 148662917adc0faf10568197b9def86af711e842 Mon Sep 17 00:00:00 2001 From: John Moshakis Date: Wed, 4 Oct 2023 15:59:18 -0400 Subject: [PATCH 52/83] Use derived commandText (#5298) Fixes #5297 (cherry picked from commit 94b9478e65ec3e6d1dee23ba72ce6607622109ab) --- src/Npgsql/NpgsqlCommand.cs | 2 +- test/Npgsql.Tests/BatchTests.cs | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 743187bdca..88267749af 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -923,7 +923,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; diff --git a/test/Npgsql.Tests/BatchTests.cs b/test/Npgsql.Tests/BatchTests.cs index e59d3b9195..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() { From a95b5aa183330c21525461132fe4508d3735c046 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 29 Oct 2023 18:02:02 +0300 Subject: [PATCH 53/83] Fix query cancellation on timeout for netstandard (#5357) Fixes #5356 (cherry picked from commit 6b1e70c677068d10870a14bf22b5c34485102395) --- src/Npgsql/Internal/NpgsqlReadBuffer.cs | 53 ++++++++++++------------- 1 file changed, 25 insertions(+), 28 deletions(-) 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; } From 5e9a20cdc6acd0d8a252791540d32d8e598fa481 Mon Sep 17 00:00:00 2001 From: Brar Piening Date: Tue, 7 Nov 2023 22:00:03 +0100 Subject: [PATCH 54/83] Consume the stream in LogicalDecodingMessage if the API consumer doesn't (#5233) Closes #5208 (cherry picked from commit 7d69a92aa489a8612d65c53989c2b5ab887d7f26) --- .../PgOutput/PgOutputAsyncEnumerable.cs | 1 + .../Replication/PgOutputReplicationTests.cs | 45 ++++++++++++------- 2 files changed, 31 insertions(+), 15 deletions(-) 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/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(); } From f819b9f30c5a4005c3411a5a97e996144c777820 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Mon, 27 Nov 2023 21:28:27 +0300 Subject: [PATCH 55/83] Fix explicit batch preparation reset (#5459) Fixes #5458 (cherry picked from commit 9e3a8dffe7dbdde13767e38a674bdeb0b3828d11) --- src/Npgsql/NpgsqlCommand.cs | 1 + test/Npgsql.Tests/PrepareTests.cs | 125 ++++++++++++++++++++++ test/Npgsql.Tests/Support/PgServerMock.cs | 14 +++ 3 files changed, 140 insertions(+) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 88267749af..a159812bb6 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -660,6 +660,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) diff --git a/test/Npgsql.Tests/PrepareTests.cs b/test/Npgsql.Tests/PrepareTests.cs index bb539e6e04..a0b9c96a39 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() { @@ -793,6 +797,127 @@ public async Task Explicit_prepare_unprepare_many_queries() 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) { var conn = OpenConnection(connectionString); diff --git a/test/Npgsql.Tests/Support/PgServerMock.cs b/test/Npgsql.Tests/Support/PgServerMock.cs index 6a83cc0248..c9b61d8226 100644 --- a/test/Npgsql.Tests/Support/PgServerMock.cs +++ b/test/Npgsql.Tests/Support/PgServerMock.cs @@ -225,6 +225,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(); From d5c34c866dd74003e6d6a8534ba3beea0cd61ab3 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 25 Jan 2024 16:29:39 +0300 Subject: [PATCH 56/83] Stop sending Host with SslStream if it's an IP address (#5547) Fixes #5543 (cherry picked from commit 7087812c0182df2177fc98d1e11084684038e9de) --- src/Npgsql/Internal/NpgsqlConnector.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index 25262b323a..16a9298b16 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -378,7 +378,7 @@ internal NpgsqlConnector(NpgsqlDataSource dataSource, NpgsqlConnection conn) _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... @@ -659,7 +659,7 @@ internal async ValueTask QueryDatabaseState( reader.NextResult(); reader.Read(); } - + _isTransactionReadOnly = reader.GetString(0) != "off"; var databaseState = UpdateDatabaseState(); @@ -876,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 @@ -889,9 +901,9 @@ async Task RawOpen(SslMode sslMode, NpgsqlTimeout timeout, bool async, Cancellat #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; } @@ -2076,7 +2088,7 @@ internal Exception Break(Exception reason) Monitor.Exit(CleanupLock); } } - + void FullCleanup() { lock (CleanupLock) From 6cc52241d2b481daaa39cb0d454dba3429000fdd Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 25 Jan 2024 16:54:39 +0300 Subject: [PATCH 57/83] Up SqlClient version to unblock CI 5.0.0 has vulnerability --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ - + From a22a42d8141d7a3528f43c02c095a409507cf1af Mon Sep 17 00:00:00 2001 From: Nino Floris Date: Thu, 9 May 2024 13:20:06 +0200 Subject: [PATCH 58/83] Merge pull request from GHSA-x9vc-6hfv-hg8c --- .../NpgsqlConnector.FrontendMessages.cs | 47 +++++++--- src/Npgsql/Internal/NpgsqlWriteBuffer.cs | 64 ++++++++++++- src/Npgsql/NpgsqlTransaction.cs | 11 +-- test/Npgsql.Tests/CommandTests.cs | 91 +++++++++++++++++++ test/Npgsql.Tests/Support/PgPostmasterMock.cs | 1 + test/Npgsql.Tests/Support/PgServerMock.cs | 1 + 6 files changed, 187 insertions(+), 28 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs b/src/Npgsql/Internal/NpgsqlConnector.FrontendMessages.cs index c38f39575a..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,6 +256,7 @@ 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 + WriteBuffer.StartMessage(len); if (WriteBuffer.WriteSpaceLeft < len) return FlushAndWrite(len, type, name, async, cancellationToken); @@ -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/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/NpgsqlTransaction.cs b/src/Npgsql/NpgsqlTransaction.cs index 9e21f7478c..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; } diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index e2482446b7..b5f9f9c017 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1008,6 +1008,96 @@ 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() + { + await using var conn = CreateConnection(); + await conn.OpenAsync(); + 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() + { + await using var connection = await OpenConnectionAsync(); + + var largeString = new string('A', 1 << 29); + + await using var cmd = connection.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() @@ -1183,6 +1273,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) 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 c9b61d8226..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) From 1120a85f66c7a617bb2f549b63c49699c55d57d8 Mon Sep 17 00:00:00 2001 From: Nino Floris Date: Thu, 9 May 2024 14:30:20 +0200 Subject: [PATCH 59/83] Fix replication message writing (cherry picked from commit 1be7b84315b2cc45c19c8324d07632354d7be7fa) --- src/Npgsql/Replication/ReplicationConnection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Npgsql/Replication/ReplicationConnection.cs b/src/Npgsql/Replication/ReplicationConnection.cs index 4c185d0985..fac363e0f6 100644 --- a/src/Npgsql/Replication/ReplicationConnection.cs +++ b/src/Npgsql/Replication/ReplicationConnection.cs @@ -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? From 84bdb9b8c61c08bf3673cbd6a8633f76553eaf49 Mon Sep 17 00:00:00 2001 From: Nino Floris Date: Thu, 9 May 2024 15:31:44 +0200 Subject: [PATCH 60/83] Suppress logging and stream analyzer rules --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index da195c1000..87ef27bef7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,7 +19,7 @@ true snupkg true - $(NoWarn);NETSDK1138 + $(NoWarn);NETSDK1138;CA2017;CA2022 true From 8882a3983a3552c92ba9ba1ca171a664bba53b60 Mon Sep 17 00:00:00 2001 From: Nino Floris Date: Thu, 9 May 2024 15:50:39 +0200 Subject: [PATCH 61/83] Remove unsupported PG 11 from CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 049e8fd91f..02479e9f86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-2022] - pg_major: [16, 15, 14, 13, 12, 11] + pg_major: [16, 15, 14, 13, 12] config: [Release] test_tfm: [net7.0] include: From f560a05ae0696f844cffbddf2d6e2abfe233ec58 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 9 May 2024 16:19:52 +0200 Subject: [PATCH 62/83] Bump version to 7.0.8 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 87ef27bef7..bae9ffea3b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.7 + 7.0.8 latest true enable From b1bd52327f9fef3f61d7aba521ac47ee1ce674d4 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sat, 18 May 2024 22:08:28 +0300 Subject: [PATCH 63/83] Fix command text with batching for OpenTelemetry (#5706) Fixes #5660 (cherry picked from commit dc0d22edb1cd79d465726a431fcab69c84b80ba9) --- src/Npgsql/Internal/NpgsqlConnector.Auth.cs | 9 +++++---- src/Npgsql/NpgsqlCommand.cs | 22 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs index 842befed88..6c4e7dd3cd 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.Auth.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.Auth.cs @@ -57,7 +57,7 @@ async Task Authenticate(string username, NpgsqlTimeout timeout, bool async, Canc 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]; @@ -156,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(); @@ -238,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; diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index a159812bb6..d6a40e71e7 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. /// @@ -1642,7 +1662,7 @@ internal void TraceCommandStart(NpgsqlConnector connector) { Debug.Assert(CurrentActivity is null); if (NpgsqlActivitySource.IsEnabled) - CurrentActivity = NpgsqlActivitySource.CommandStart(connector, CommandText, CommandType); + CurrentActivity = NpgsqlActivitySource.CommandStart(connector, IsWrappedByBatch ? GetBatchFullCommandText() : CommandText, CommandType); } internal void TraceReceivedFirstResponse() From 25aaaa2fad2ca3d06274cd09b03ebad9497cd50b Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 19 May 2024 09:44:31 +0300 Subject: [PATCH 64/83] Fix message overflow tests with multiplexing (#5713) (cherry picked from commit 4c06c69404a898acf42f701701681a9db1da637a) --- test/Npgsql.Tests/CommandTests.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index b5f9f9c017..9b057e968b 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1011,8 +1011,9 @@ public async Task Use_across_connection_change([Values(PrepareOrNot.Prepared, Pr [Test] public async Task Parameter_overflow_message_length_throws() { - await using var conn = CreateConnection(); - await conn.OpenAsync(); + // 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); @@ -1076,11 +1077,13 @@ record BigComposite [Test] public async Task Array_overflow_message_length_throws() { - await using var connection = await OpenConnectionAsync(); + // 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 = connection.CreateCommand(); + await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT @a"; var array = new[] { From da1def7a6a3802d5177b7d77f541ab118163898f Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 19 May 2024 23:00:24 +0300 Subject: [PATCH 65/83] Reset AllResultTypesAreUnknown and UnknownResultTypeList for cached commands (#5712) Fixes #5690 (cherry picked from commit 1d68fb99b38b56aa0f4629ada84edb1810eba241) --- src/Npgsql/NpgsqlCommand.cs | 3 +++ test/Npgsql.Tests/CommandTests.cs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index d6a40e71e7..4212cd7f06 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1645,6 +1645,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; } diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index 9b057e968b..cf2e3ca222 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -1193,11 +1193,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); @@ -1210,6 +1214,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); } From 3b2bd3849d6a87c4137a1a0cdbcd310ccec01a8d Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 2 Jul 2024 17:18:01 +0300 Subject: [PATCH 66/83] Fix tracing activity leak with multiplexing (#5765) Fixes #5764 (cherry picked from commit 792b5d23ec32ae282e51c22898efba99932c5bcb) --- src/Npgsql/MultiplexingDataSource.cs | 2 +- src/Npgsql/NpgsqlActivitySource.cs | 31 +++++++++++++++------------- src/Npgsql/NpgsqlCommand.cs | 16 +++++++++----- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Npgsql/MultiplexingDataSource.cs b/src/Npgsql/MultiplexingDataSource.cs index 137b60832a..ee4c3e4a05 100644 --- a/src/Npgsql/MultiplexingDataSource.cs +++ b/src/Npgsql/MultiplexingDataSource.cs @@ -173,7 +173,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/NpgsqlActivitySource.cs b/src/Npgsql/NpgsqlActivitySource.cs index ae6f46956d..1aa188c11e 100644 --- a/src/Npgsql/NpgsqlActivitySource.cs +++ b/src/Npgsql/NpgsqlActivitySource.cs @@ -21,11 +21,9 @@ static NpgsqlActivitySource() internal static bool IsEnabled => Source.HasListeners(); - internal static Activity? CommandStart(NpgsqlConnector connector, string commandText, CommandType commandType) + internal static Activity? CommandStart(NpgsqlConnectionStringBuilder settings, string commandText, CommandType commandType) { - var settings = connector.Settings; - - var dbName = settings.Database ?? connector.InferredUserName; + var dbName = settings.Database ?? "UNKNOWN"; string? dbOperation = null; string? dbSqlTable = null; string activityName; @@ -61,18 +59,25 @@ static NpgsqlActivitySource() 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) + { activity.SetTag("db.system", "postgresql"); activity.SetTag("db.connection_string", connector.UserFacingConnectionString); 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", dbName); - activity.SetTag("db.statement", commandText); + activity.SetTag("db.name", connector.Settings.Database ?? connector.InferredUserName); activity.SetTag("db.connection_id", connector.Id); - if (dbOperation != null) - activity.SetTag("db.operation", dbOperation); - if (dbSqlTable != null) - activity.SetTag("db.sql.table", dbSqlTable); var endPoint = connector.ConnectedEndPoint; Debug.Assert(endPoint is not null); @@ -83,19 +88,17 @@ 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) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 4212cd7f06..c605e1f663 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1467,7 +1467,7 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior } NpgsqlEventSource.Log.CommandStart(CommandText); - TraceCommandStart(connector); + TraceCommandEnrich(connector); // If a cancellation is in progress, wait for it to "complete" before proceeding (#615) connector.ResetCancellation(); @@ -1540,6 +1540,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. @@ -1661,19 +1663,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, IsWrappedByBatch ? GetBatchFullCommandText() : CommandText, CommandType); + 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() From 8a4d95b732f203073789b2820f039ae8a45058d8 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 2 Jul 2024 17:44:12 +0300 Subject: [PATCH 67/83] Do not enrich activity if IsAllDataRequested = false --- src/Npgsql/NpgsqlActivitySource.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Npgsql/NpgsqlActivitySource.cs b/src/Npgsql/NpgsqlActivitySource.cs index 1aa188c11e..9db0883cc4 100644 --- a/src/Npgsql/NpgsqlActivitySource.cs +++ b/src/Npgsql/NpgsqlActivitySource.cs @@ -71,6 +71,9 @@ static NpgsqlActivitySource() 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", connector.InferredUserName); @@ -103,6 +106,9 @@ internal static void Enrich(Activity activity, NpgsqlConnector connector) internal static void ReceivedFirstResponse(Activity activity) { + if (!activity.IsAllDataRequested) + return; + var activityEvent = new ActivityEvent("received-first-response"); activity.AddEvent(activityEvent); } @@ -128,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 +} From 1768660b5cd887497b6667ca7cd251ff2052c8b1 Mon Sep 17 00:00:00 2001 From: Ede Meijer Date: Fri, 28 Jun 2024 15:37:52 +0200 Subject: [PATCH 68/83] Fix connect timeout when resolving to multiple IPs (#5739) (cherry picked from commit 447387cdb0281224a85be385fe9b75820a5b6f18) --- src/Npgsql/Internal/NpgsqlConnector.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index 16a9298b16..02e121a7b5 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -1028,17 +1028,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 = timeout.CheckAndGetTimeLeft() / 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 = @@ -1049,7 +1048,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; From a7ec8947e78733eca95d9acdee1fab60fb60d93d Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 2 Jul 2024 18:06:11 +0300 Subject: [PATCH 69/83] Fix compilation for netstandard 2.0 --- src/Npgsql/Internal/NpgsqlConnector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index 02e121a7b5..9a7ec0fb58 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -1031,7 +1031,7 @@ Task GetHostAddressesAsync(CancellationToken ct) => // Give each endpoint an equal share of the remaining time var perEndpointTimeout = default(TimeSpan); if (timeout.IsSet) - perEndpointTimeout = timeout.CheckAndGetTimeLeft() / endpoints.Length; + perEndpointTimeout = TimeSpan.FromTicks(timeout.CheckAndGetTimeLeft().Ticks / endpoints.Length); for (var i = 0; i < endpoints.Length; i++) { From 81fd2d6565b5ae33074502279d1ddf9888566418 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 11 Jul 2024 19:04:39 +0200 Subject: [PATCH 70/83] Bump System.Text.Json to 8.0.4 (#5787) (cherry picked from commit a9bfb4a07c1db1e8c7b711b978c6f5a2aa57a958) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7587a1cf86..1474764689 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + From 6304a9601b92e283e365472be4898ecd96755b6c Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Wed, 4 Sep 2024 18:23:40 +0300 Subject: [PATCH 71/83] Add support for tls1.3 for .NET Framework for some reason (#5823) (cherry picked from commit d03c487b1135213d338c2dde6956d32e34c5b907) --- .../Internal/NpgsqlConnector.NetStandard.cs | 54 +++++++++++++++++++ src/Npgsql/Internal/NpgsqlConnector.cs | 7 ++- 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/Npgsql/Internal/NpgsqlConnector.NetStandard.cs 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 9a7ec0fb58..bc8365a29d 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -895,9 +895,12 @@ 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) From aae81db5069f1a0be6f92b81c8a83bb50ce47f93 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 4 Sep 2024 17:48:20 +0200 Subject: [PATCH 72/83] Revert "Bump System.Text.Json to 8.0.4 (#5787)" This reverts commit 81fd2d6565b5ae33074502279d1ddf9888566418. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1474764689..7587a1cf86 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + From 788830445cc47fe62d5fce947cf1acd6749e863f Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 4 Sep 2024 17:54:02 +0200 Subject: [PATCH 73/83] Suppress nuget audit for System.Text.Json security advisory 7.0 is out of date, so no version of S.T.Json 7.0.x exists without the advisory --- Directory.Build.props | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index bae9ffea3b..0303f6883b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,4 +33,8 @@ + + + + From 9c53c448545406ec5a5ef0900511ebc7c7980fa1 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Thu, 5 Sep 2024 13:40:26 +0300 Subject: [PATCH 74/83] Fix unpooled connection return with multiple hosts (#5784) Fixes #5783 (cherry picked from commit 3fce77d7ce92b4ac984ce3afb4364b23b791fb05) --- src/Npgsql/Internal/NpgsqlConnector.cs | 3 --- src/Npgsql/MultiplexingDataSource.cs | 5 ++--- src/Npgsql/NpgsqlMultiHostDataSource.cs | 2 +- src/Npgsql/PoolingDataSource.cs | 12 +----------- src/Npgsql/VolatileResourceManager.cs | 8 ++++++-- test/Npgsql.Tests/SystemTransactionTests.cs | 11 +++++++---- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index bc8365a29d..ba930aa000 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -1979,9 +1979,6 @@ internal void Close() LogMessages.ClosedPhysicalConnection(ConnectionLogger, Host, Port, Database, UserFacingConnectionString, Id); } - internal bool TryRemovePendingEnlistedConnector(Transaction transaction) - => DataSource.TryRemovePendingEnlistedConnector(this, transaction); - internal void Return() => DataSource.Return(this); /// diff --git a/src/Npgsql/MultiplexingDataSource.cs b/src/Npgsql/MultiplexingDataSource.cs index ee4c3e4a05..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); 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/PoolingDataSource.cs b/src/Npgsql/PoolingDataSource.cs index 5bdcadad69..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() diff --git a/src/Npgsql/VolatileResourceManager.cs b/src/Npgsql/VolatileResourceManager.cs index 70afea0557..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; @@ -275,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(); } diff --git a/test/Npgsql.Tests/SystemTransactionTests.cs b/test/Npgsql.Tests/SystemTransactionTests.cs index 44abc2b24d..3150e2cb83 100644 --- a/test/Npgsql.Tests/SystemTransactionTests.cs +++ b/test/Npgsql.Tests/SystemTransactionTests.cs @@ -293,14 +293,17 @@ public void Single_unpooled_connection() scope.Complete(); } - [Test, IssueLink("https://github.com/npgsql/npgsql/issues/4963")] - public void Single_unpooled_closed_connection() + [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()) @@ -309,11 +312,11 @@ public void Single_unpooled_closed_connection() { cmd.ExecuteNonQuery(); conn.Close(); - Assert.That(dataSource.Statistics.Total, Is.EqualTo(1)); + Assert.That(pooling ? dataSource.Statistics.Busy : dataSource.Statistics.Total, Is.EqualTo(1)); scope.Complete(); } - Assert.That(dataSource.Statistics.Total, Is.EqualTo(0)); + Assert.That(pooling ? dataSource.Statistics.Busy : dataSource.Statistics.Total, Is.EqualTo(0)); } [Test] From cd786a5f2f919643c27dbda6e40016118f6f8443 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 10 Sep 2024 17:02:03 +0300 Subject: [PATCH 75/83] Fix sending cancellation request if it's requested while reading prepended responses (#5774) Fixes #5191 (cherry picked from commit 8473d729e667a4db860c22335509484c4eb268f8) --- src/Npgsql/Internal/NpgsqlConnector.cs | 91 +++++++++++++++++--------- src/Npgsql/NpgsqlBinaryExporter.cs | 2 +- src/Npgsql/NpgsqlCommand.cs | 9 +-- test/Npgsql.Tests/CommandTests.cs | 44 ++++++++++++- 4 files changed, 109 insertions(+), 37 deletions(-) diff --git a/src/Npgsql/Internal/NpgsqlConnector.cs b/src/Npgsql/Internal/NpgsqlConnector.cs index ba930aa000..83d6b01473 100644 --- a/src/Npgsql/Internal/NpgsqlConnector.cs +++ b/src/Npgsql/Internal/NpgsqlConnector.cs @@ -1332,6 +1332,12 @@ internal ValueTask ReadMessage(bool async, DataRowLoadingMode d // 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 (Exception e) { @@ -1714,7 +1720,7 @@ internal void ResetCancellation() } } - internal void PerformUserCancellation() + internal void PerformImmediateUserCancellation() { var connection = Connection; if (connection is null || connection.ConnectorBindingScope == ConnectorBindingScope.Reader || UserCancellationRequested) @@ -1734,36 +1740,43 @@ internal void PerformUserCancellation() try { - // Wait before we've read all responses for the prepended queries - // as we can't gracefully handle their cancellation. - // Break makes sure that it's going to be set even if we fail while reading them. + // 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; + // 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; - _userCancellationRequested = true; - - 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); - } + PerformUserCancellationUnsynchronized(); + } + finally + { + Monitor.Exit(CancelLock); + } + } - return; - } - } + 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); + } - UserTimeout = -1; - ReadBuffer.Timeout = _cancelImmediatelyTimeout; - ReadBuffer.Cts.Cancel(); + try + { + PerformUserCancellationUnsynchronized(); } finally { @@ -1771,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. @@ -1853,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); } /// @@ -1884,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 @@ -2016,11 +2052,6 @@ internal Exception Break(Exception reason) try { - // If we're broken while reading prepended messages - // the cancellation request might still be waiting on the MRE. - // Unblock it. - ReadingPrependedMessagesMRE.Set(); - LogMessages.BreakingConnection(ConnectionLogger, Id, reason); // Note that we may be reading and writing from the same connector concurrently, so safely set 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 c605e1f663..39652a9d68 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1456,6 +1456,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)) @@ -1469,9 +1473,6 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior NpgsqlEventSource.Log.CommandStart(CommandText); TraceCommandEnrich(connector); - // If a cancellation is in progress, wait for it to "complete" before proceeding (#615) - connector.ResetCancellation(); - // 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 // entire send to complete would trigger a deadlock for multi-statement commands, @@ -1625,7 +1626,7 @@ public override void Cancel() if (connector is null) return; - connector.PerformUserCancellation(); + connector.PerformImmediateUserCancellation(); } #endregion Cancel diff --git a/test/Npgsql.Tests/CommandTests.cs b/test/Npgsql.Tests/CommandTests.cs index cf2e3ca222..993c151d25 100644 --- a/test/Npgsql.Tests/CommandTests.cs +++ b/test/Npgsql.Tests/CommandTests.cs @@ -280,7 +280,6 @@ public async Task Prepare_timeout_hard([Values] SyncOrAsync async) #region Cancel [Test, Description("Basic cancellation scenario")] - [Ignore("Flaky, see https://github.com/npgsql/npgsql/issues/5070")] public async Task Cancel() { if (IsMultiplexing) @@ -320,7 +319,6 @@ public async Task Cancel_async_immediately() } [Test, Description("Cancels an async query with the cancellation token, with successful PG cancellation")] - [Explicit("Flaky due to #5033")] public async Task Cancel_async_soft() { if (IsMultiplexing) @@ -341,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() { From 528c7fc30274c1d649d3ccd6c7fae5b5c650da1d Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 10 Sep 2024 17:29:00 +0200 Subject: [PATCH 76/83] Bump version to 7.0.9 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 0303f6883b..f8f721dcc4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.8 + 7.0.9 latest true enable From aaed546f2d3595c1a5b13ac4b80e600425e7f27d Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Mon, 7 Oct 2024 21:55:22 +0300 Subject: [PATCH 77/83] Fix explicit prepare/unprepare after error 0A000 (#5869) Fixes #5864 (cherry picked from commit 39e38fd1d5ef354b27cdd70c632877660b24ac87) --- src/Npgsql/NpgsqlBatchCommand.cs | 6 +----- src/Npgsql/PreparedStatement.cs | 3 ++- src/Npgsql/PreparedStatementManager.cs | 7 +++++-- test/Npgsql.Tests/PrepareTests.cs | 24 +++++++++++++++++++++--- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Npgsql/NpgsqlBatchCommand.cs b/src/Npgsql/NpgsqlBatchCommand.cs index 9e45f45c99..f60c3b57b8 100644 --- a/src/Npgsql/NpgsqlBatchCommand.cs +++ b/src/Npgsql/NpgsqlBatchCommand.cs @@ -256,11 +256,7 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg) OID = msg.OID; } - internal void ResetPreparation() - { - PreparedStatement = null; - ConnectorPreparedOn = null; - } + internal void ResetPreparation() => ConnectorPreparedOn = null; /// /// Returns the . 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/test/Npgsql.Tests/PrepareTests.cs b/test/Npgsql.Tests/PrepareTests.cs index a0b9c96a39..8cc3122a49 100644 --- a/test/Npgsql.Tests/PrepareTests.cs +++ b/test/Npgsql.Tests/PrepareTests.cs @@ -754,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) { @@ -773,12 +773,30 @@ 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")] From 080faab31d6d75fa255caa792b03b2dd22748a35 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Mon, 7 Oct 2024 21:58:16 +0300 Subject: [PATCH 78/83] Fix writing non-normalized Nodatime's periods (#5868) Fixes #5867 (cherry picked from commit 7062c5cb3296f3b984abdd92e7c08f5b3ed98ac7) --- .../Internal/IntervalHandler.cs | 4 ++- test/Npgsql.NodaTime.Tests/NodaTimeTests.cs | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) 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/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 From 5ac0d1d63700af165ecea37e25e39538807862b1 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Mon, 7 Oct 2024 22:13:57 +0300 Subject: [PATCH 79/83] Revert "Fix explicit prepare/unprepare after error 0A000 (#5869)" This reverts commit aaed546f2d3595c1a5b13ac4b80e600425e7f27d. --- src/Npgsql/NpgsqlBatchCommand.cs | 6 +++++- src/Npgsql/PreparedStatement.cs | 3 +-- src/Npgsql/PreparedStatementManager.cs | 7 ++----- test/Npgsql.Tests/PrepareTests.cs | 24 +++--------------------- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/Npgsql/NpgsqlBatchCommand.cs b/src/Npgsql/NpgsqlBatchCommand.cs index f60c3b57b8..9e45f45c99 100644 --- a/src/Npgsql/NpgsqlBatchCommand.cs +++ b/src/Npgsql/NpgsqlBatchCommand.cs @@ -256,7 +256,11 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg) OID = msg.OID; } - internal void ResetPreparation() => ConnectorPreparedOn = null; + internal void ResetPreparation() + { + PreparedStatement = null; + ConnectorPreparedOn = null; + } /// /// Returns the . diff --git a/src/Npgsql/PreparedStatement.cs b/src/Npgsql/PreparedStatement.cs index e8e1d63d8c..015adc5dd3 100644 --- a/src/Npgsql/PreparedStatement.cs +++ b/src/Npgsql/PreparedStatement.cs @@ -24,8 +24,7 @@ sealed class PreparedStatement internal PreparedState State { get; set; } - // Invalidated statement is still prepared and allocated on PG's side - internal bool IsPrepared => State is PreparedState.Prepared or PreparedState.Invalidated; + internal bool IsPrepared => State == PreparedState.Prepared; /// /// 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 7a74c2035a..c7f18c52e5 100644 --- a/src/Npgsql/PreparedStatementManager.cs +++ b/src/Npgsql/PreparedStatementManager.cs @@ -60,8 +60,7 @@ internal PreparedStatementManager(NpgsqlConnector connector) if (BySql.TryGetValue(sql, out var pStatement)) { Debug.Assert(pStatement.State != PreparedState.Unprepared); - // If statement is invalidated, fall through below where we replace it with another - if (pStatement.IsExplicit && pStatement.State != PreparedState.Invalidated) + if (pStatement.IsExplicit) { // Great, we've found an explicit prepared statement. // We just need to check that the parameter types correspond, since prepared statements are @@ -78,10 +77,8 @@ 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/test/Npgsql.Tests/PrepareTests.cs b/test/Npgsql.Tests/PrepareTests.cs index 8cc3122a49..a0b9c96a39 100644 --- a/test/Npgsql.Tests/PrepareTests.cs +++ b/test/Npgsql.Tests/PrepareTests.cs @@ -754,7 +754,7 @@ public void Multiplexing_not_supported() } [Test] - public async Task Explicitly_prepared_statement_invalidation([Values] bool prepareAfterError, [Values] bool unprepareAfterError) + public async Task Explicitly_prepared_statement_invalidation() { var csb = new NpgsqlConnectionStringBuilder(ConnectionString) { @@ -773,30 +773,12 @@ public async Task Explicitly_prepared_statement_invalidation([Values] bool prepa // 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()); - if (!prepareAfterError) - { - // The command is unprepared, though. It's the user's responsibility to re-prepare if they wish. - Assert.False(command.IsPrepared); - } + // 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")] From bc0495a8118bb4bdc2d758abd7b58c182975b422 Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 8 Oct 2024 15:03:14 +0300 Subject: [PATCH 80/83] Fix explicit preparation replacing automatically prepared one (#5874) Fixes #5873 (cherry picked from commit 602ef9aa96eeb4699464565da0fb82ed03ef354a) --- src/Npgsql/NpgsqlCommand.cs | 9 +++++++-- test/Npgsql.Tests/AutoPrepareTests.cs | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 39652a9d68..3051a6c1e0 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -726,11 +726,16 @@ static async Task PrepareLong(NpgsqlCommand command, bool async, NpgsqlConnector continue; var pStatement = batchCommand.PreparedStatement!; + var replacedStatement = pStatement.StatementBeingReplaced; - if (pStatement.StatementBeingReplaced != null) + if (replacedStatement != null) { Expect(await connector.ReadMessage(async), connector); - pStatement.StatementBeingReplaced.CompleteUnprepare(); + replacedStatement.CompleteUnprepare(); + + if (!replacedStatement.IsExplicit) + connector.PreparedStatementManager.AutoPrepared[replacedStatement.AutoPreparedSlotIndex] = null; + pStatement.StatementBeingReplaced = null; } diff --git a/test/Npgsql.Tests/AutoPrepareTests.cs b/test/Npgsql.Tests/AutoPrepareTests.cs index d3aff1c07d..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] From 6b376a2d477a0a883cc4b453b96cabbeb59dfbbc Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Tue, 8 Oct 2024 15:10:52 +0300 Subject: [PATCH 81/83] Reapply "Fix explicit prepare/unprepare after error 0A000 (#5869)" This reverts commit 5ac0d1d63700af165ecea37e25e39538807862b1. --- src/Npgsql/NpgsqlBatchCommand.cs | 6 +----- src/Npgsql/PreparedStatement.cs | 3 ++- src/Npgsql/PreparedStatementManager.cs | 7 +++++-- test/Npgsql.Tests/PrepareTests.cs | 24 +++++++++++++++++++++--- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Npgsql/NpgsqlBatchCommand.cs b/src/Npgsql/NpgsqlBatchCommand.cs index 9e45f45c99..f60c3b57b8 100644 --- a/src/Npgsql/NpgsqlBatchCommand.cs +++ b/src/Npgsql/NpgsqlBatchCommand.cs @@ -256,11 +256,7 @@ internal void ApplyCommandComplete(CommandCompleteMessage msg) OID = msg.OID; } - internal void ResetPreparation() - { - PreparedStatement = null; - ConnectorPreparedOn = null; - } + internal void ResetPreparation() => ConnectorPreparedOn = null; /// /// Returns the . 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/test/Npgsql.Tests/PrepareTests.cs b/test/Npgsql.Tests/PrepareTests.cs index a0b9c96a39..8cc3122a49 100644 --- a/test/Npgsql.Tests/PrepareTests.cs +++ b/test/Npgsql.Tests/PrepareTests.cs @@ -754,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) { @@ -773,12 +773,30 @@ 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")] From cbc1151ef18cbe4ca06a54e8c237bd23c069123c Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 16 Mar 2025 18:45:34 +0300 Subject: [PATCH 82/83] Bump version to 7.0.10 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index f8f721dcc4..5947fbb233 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 7.0.9 + 7.0.10 latest true enable From e113e7cec2e09fa649c671eb05010c66fc2f234e Mon Sep 17 00:00:00 2001 From: Nikita Kazmin Date: Sun, 16 Mar 2025 19:52:11 +0300 Subject: [PATCH 83/83] Fix tracing for Npgsql 7 (#6050) Fixes #6049 --------- Co-authored-by: Shay Rojansky --- .github/workflows/build.yml | 2 +- src/Npgsql/NpgsqlCommand.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02479e9f86..e300c68775 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-2022] - pg_major: [16, 15, 14, 13, 12] + pg_major: [16, 15, 14, 13] config: [Release] test_tfm: [net7.0] include: diff --git a/src/Npgsql/NpgsqlCommand.cs b/src/Npgsql/NpgsqlCommand.cs index 3051a6c1e0..1be30f29a3 100644 --- a/src/Npgsql/NpgsqlCommand.cs +++ b/src/Npgsql/NpgsqlCommand.cs @@ -1476,6 +1476,7 @@ internal virtual async ValueTask ExecuteReader(CommandBehavior } NpgsqlEventSource.Log.CommandStart(CommandText); + TraceCommandStart(connector.Settings); TraceCommandEnrich(connector); // We do not wait for the entire send to complete before proceeding to reading -