Summary

Class:CBAM.SQL.PostgreSQL.Implementation.PostgreSQLProtocol
Assembly:CBAM.SQL.PostgreSQL.Implementation
File(s):/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Types.cs
Covered lines:1307
Uncovered lines:108
Coverable lines:1415
Total lines:2368
Line coverage:92.3%
Branch coverage:74%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.ctor(...)1800.958%0.778%
CreateReservationObject(...)201%1%
ValidateStatementOrThrow(...)800.929%0.875%
GetVariablesForExtendedQuerySequence(...)1001%0.7%
GetIOArgs(...)401%1%
ExecuteStatementAsBatch()2300.889%1%
SendMessagesForBatch()701%1%
ReceiveMessagesForBatch()1400.737%1%
ExecuteStatementAsPrepared()2500.833%1%
>c__DisplayClass20_1/<<ExecuteStatementAsPrepared()401%0.5%
ExecuteStatementAsSimple()601%1%
>c__DisplayClass21_0/<<ExecuteStatementAsSimple()3801%0.789%
MoveNextAsync()101%1%
>c__DisplayClass22_0/<<MoveNextAsync()1201%0.667%
PerformDisposeStatementAsync()1300.833%1%
ConvertFromBytes()1100.84%1%
ReadMessagesUntilMeaningful()1700.545%1%
PerformClose()101%1%
SocketHasDataPending()401%0.5%
CheckNotificationsAsync()600.438%1%
>c__DisplayClass55_1/<<CheckNotificationsAsync()600%0%
ListenToNotificationsAsync()101%0%
>c__DisplayClass56_0/<<ListenToNotificationsAsync()601%0.667%
PerformStartup()1701%1%
DoConnectionInitialization()3700.863%1%
ProcessAuth()1300.833%1%
GetPasswordBytes(...)401%0.5%
HandleMD5Authentication(...)600.971%0.833%
HandleSASLAuthentication_Start(...)1600.953%0.688%
HandleSASLAuthentication_Continue(...)400.667%0.5%
HandleSASLAuthentication_Final(...)600.667%0.5%
HandleSASLAuthentication_ContinueOrFinal(...)101%0%
DisposeSASLState(...)801%0.625%
.ctor(...)101%0%
RFQSeen()101%0%
.cctor()101%0%
ReadTypesFromServer()1201%1%
CreateDefaultTypesInfos()15001%1%
CreateSingleBodyInfoWithType(...)101%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using CBAM.Abstractions;
 19using CBAM.Abstractions.Implementation;
 20using CBAM.SQL.Implementation;
 21using CBAM.SQL.PostgreSQL;
 22using CBAM.SQL.PostgreSQL.Implementation;
 23using System;
 24using System.Collections.Generic;
 25using System.IO;
 26using System.Linq;
 27using System.Net;
 28using System.Text;
 29using System.Threading;
 30using System.Threading.Tasks;
 31using UtilPack;
 32using MessageIOArgs = System.ValueTuple<CBAM.SQL.PostgreSQL.BackendABIHelper, System.IO.Stream, System.Threading.Cancell
 33using TBoundTypeInfo = System.ValueTuple<System.Type, CBAM.SQL.PostgreSQL.PgSQLTypeFunctionality, CBAM.SQL.PostgreSQL.Pg
 34using FluentCryptography.SASL;
 35using FluentCryptography.SASL.SCRAM;
 36using AsyncEnumeration.Implementation.Enumerable;
 37using AsyncEnumeration.Implementation.Provider;
 38
 39#if !NETSTANDARD1_0
 40using System.Net.Sockets;
 41#endif
 42
 43namespace CBAM.SQL.PostgreSQL.Implementation
 44{
 45   using TSASLAuthState = System.ValueTuple<SASLMechanism, SASLCredentialsSCRAMForClient, ResizableArray<Byte>, IEncodin
 46   using TStatementExecutionSimpleTaskParameter = System.ValueTuple<SQLStatementExecutionResult, Func<ValueTask<(Boolean
 47
 48   internal sealed partial class PostgreSQLProtocol : SQLConnectionFunctionalitySU<PgSQLConnectionVendorFunctionality>
 49   {
 50
 51
 52      private Int32 _lastSeenTransactionStatus;
 53      private readonly IDictionary<String, String> _serverParameters;
 54      //private Int32 _standardConformingStrings;
 55      private readonly Version _serverVersion;
 56
 57      public PostgreSQLProtocol(
 58         PgSQLConnectionVendorFunctionality vendorFunctionality,
 59         Boolean disableBinaryProtocolSend,
 60         Boolean disableBinaryProtocolReceive,
 61         BackendABIHelper messageIOArgs,
 62         Stream stream,
 63         ResizableArray<Byte> buffer,
 64         IDictionary<String, String> serverParameters,
 65         TransactionStatus status,
 66         Int32 backendPID
 67#if !NETSTANDARD1_0
 68         , Socket socket
 69#endif
 2470         ) : base( vendorFunctionality, DefaultAsyncProvider.Instance )
 71      {
 2472         this.DisableBinaryProtocolSend = disableBinaryProtocolSend;
 2473         this.DisableBinaryProtocolReceive = disableBinaryProtocolReceive;
 2474         this.MessageIOArgs = ArgumentValidator.ValidateNotNull( nameof( messageIOArgs ), messageIOArgs );
 2475         this.Stream = ArgumentValidator.ValidateNotNull( nameof( stream ), stream );
 76#if !NETSTANDARD1_0
 2477         this.Socket = socket;
 78#endif
 2479         this.Buffer = buffer ?? new ResizableArray<Byte>( 8, exponentialResize: true );
 2480         this.DataRowColumnSizes = new ResizableArray<ResettableTransformable<Int32?, Int32>>( exponentialResize: false 
 2481         this._serverParameters = ArgumentValidator.ValidateNotNull( nameof( serverParameters ), serverParameters );
 2482         this.ServerParameters = new System.Collections.ObjectModel.ReadOnlyDictionary<String, String>( serverParameters
 2883         this.TypeRegistry = new TypeRegistryImpl( vendorFunctionality, sql => this.PrepareStatementForExecution( vendor
 84
 2485         if ( serverParameters.TryGetValue( "server_version", out var serverVersionString ) )
 86         {
 87            // Parse server version
 2488            var i = 0;
 2489            var version = serverVersionString.Trim();
 12090            while ( i < version.Length && ( Char.IsDigit( version[i] ) || version[i] == '.' ) )
 91            {
 9692               ++i;
 93            }
 2494            this._serverVersion = new Version( version.Substring( 0, i ) );
 95
 96         }
 97
 98         // Min supported version is 8.4.
 2499         var serverVersion = this._serverVersion;
 24100         if ( serverVersion != null && ( serverVersion.Major < 8 || ( serverVersion.Major == 8 && serverVersion.Minor < 
 101         {
 0102            throw new PgSQLException( "Unsupported server version: " + serverVersion + "." );
 103         }
 24104         this.LastSeenTransactionStatus = status;
 24105         this.BackendProcessID = backendPID;
 24106         this.EnqueuedNotifications = new Queue<NotificationEventArgs>();
 24107      }
 108
 507109      public TypeRegistryImpl TypeRegistry { get; }
 110
 1111      public Int32 BackendProcessID { get; }
 112
 24113      public IReadOnlyDictionary<String, String> ServerParameters { get; }
 114
 115      protected override ReservedForStatement CreateReservationObject( SQLStatementBuilderInformation stmt )
 116      {
 92117         return new PgReservedForStatement(
 92118#if DEBUG
 92119            stmt,
 92120#endif
 92121            stmt.IsSimple(),
 92122            stmt.HasBatchParameters() ? "cbam_statement" : null
 92123            );
 124      }
 125
 126      protected override void ValidateStatementOrThrow( SQLStatementBuilderInformation statement )
 127      {
 93128         ArgumentValidator.ValidateNotNull( nameof( statement ), statement );
 97129         if ( statement.BatchParameterCount > 1 )
 130         {
 131            // Verify that all columns have same typeIDs
 2132            var first = statement
 2133               .GetParametersEnumerable( 0 )
 3134               .Select( param => this.TypeRegistry.TryGetTypeInfo( param.ParameterCILType ).DatabaseData.TypeID )
 2135               .ToArray();
 2136            var max = statement.BatchParameterCount;
 12137            for ( var i = 1; i < max; ++i )
 138            {
 4139               var j = 0;
 12140               foreach ( var param in statement.GetParametersEnumerable( i ) )
 141               {
 2142                  if ( first[j] != this.TypeRegistry.TryGetTypeInfo( param.ParameterCILType ).DatabaseData.TypeID )
 143                  {
 0144                     throw new PgSQLException( "When using batch parameters, columns must have same type IDs for all bat
 145                  }
 2146                  ++j;
 147               }
 148            }
 149         }
 92150      }
 151
 152      private static (Int32[] ParameterIndices, TypeFunctionalityInformation[] TypeInfos, Int32[] TypeIDs) GetVariablesF
 153         SQLStatementBuilderInformation stmt,
 154         TypeRegistry typeRegistry,
 155         Func<SQLStatementBuilderInformation, Int32, StatementParameter> paramExtractor
 156         )
 157      {
 41158         var pCount = stmt.SQLParameterCount;
 159         TypeFunctionalityInformation[] typeInfos;
 160         Int32[] typeIDs;
 44161         if ( pCount > 0 )
 162         {
 43163            typeInfos = new TypeFunctionalityInformation[pCount];
 40164            typeIDs = new Int32[pCount];
 185165            for ( var i = 0; i < pCount; ++i )
 166            {
 50167               var param = paramExtractor( stmt, i );
 48168               var typeInfo = typeRegistry.TryGetTypeInfo( param.ParameterCILType );
 49169               typeInfos[i] = typeInfo;
 51170               typeIDs[i] = typeInfo?.DatabaseData?.TypeID ?? 0;
 171            }
 47172         }
 173         else
 174         {
 1175            typeInfos = Empty<TypeFunctionalityInformation>.Array;
 1176            typeIDs = Empty<Int32>.Array;
 177         }
 178
 48179         return (( (PgSQLStatementBuilderInformation) stmt ).ParameterIndices, typeInfos, typeIDs);
 180      }
 181
 182      private MessageIOArgs GetIOArgs( ResizableArray<Byte> bufferToUse = null, CancellationToken? tokenToUse = null )
 183      {
 774184         return (this.MessageIOArgs, this.Stream, tokenToUse ?? this.CurrentCancellationToken, bufferToUse ?? this.Buffe
 185      }
 186
 187      protected override async ValueTask<TStatementExecutionSimpleTaskParameter> ExecuteStatementAsBatch(
 188         SQLStatementBuilderInformation statement,
 189         ReservedForStatement reservedState
 190         )
 191      {
 192         // TODO somehow make statement name and chunk size parametrizable
 3193         (var parameterIndices, var typeInfos, var typeIDs) = GetVariablesForExtendedQuerySequence( statement, this.Type
 2194         var ioArgs = this.GetIOArgs();
 2195         var stmtName = ( (PgReservedForStatement) reservedState ).StatementName;
 2196         var chunkSize = 1000;
 197
 198         // Send a parse message with statement name
 2199         await new ParseMessage( statement.SQL, parameterIndices, typeIDs, stmtName ).SendMessageAsync( ioArgs, true );
 200
 201         // Now send describe message
 2202         await new DescribeMessage( true, stmtName ).SendMessageAsync( ioArgs, true );
 203
 204         // And then Flush message for backend to send responses
 2205         await FrontEndMessageWithNoContent.FLUSH.SendMessageAsync( ioArgs, false );
 206
 207         // Receive first batch of messages
 2208         BackendMessageObject msg = null;
 2209         SQLStatementExecutionResult current = null;
 2210         List<PgSQLError> notices = new List<PgSQLError>();
 2211         var sendBatch = true;
 8212         while ( msg == null )
 213         {
 6214            msg = ( await this.ReadMessagesUntilMeaningful( notices ) ).Item1;
 6215            switch ( msg )
 216            {
 217               case MessageWithNoContents nc:
 4218                  switch ( nc.Code )
 219                  {
 220                     case BackendMessageCode.ParseComplete:
 221                        // Continue reading messages
 2222                        msg = null;
 2223                        break;
 224                     case BackendMessageCode.EmptyQueryResponse:
 225                        // The statement does not produce any data, we are done
 0226                        sendBatch = false;
 0227                        break;
 228                     case BackendMessageCode.NoData:
 229                        // Do nothing, thus causing batch messages to be sent
 230                        break;
 231                     default:
 0232                        throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 233                  }
 234                  break;
 235               case RowDescription rd:
 236                  // This happens when e.g. doing SELECT schema.function(x, y, z) -> can return NULLs or rows, we don't 
 237                  break; // throw new PgSQLException( "Batch statements may only be used for non-query statements." );
 238               case ParameterDescription pd:
 2239                  if ( !ArrayEqualityComparer<Int32>.ArrayEquality( pd.ObjectIDs, typeIDs ) )
 240                  {
 0241                     throw new PgSQLException( "Backend required certain amount of parameters, but either they were not 
 242                  }
 243                  // Continue to RowDescription/NoData message
 2244                  msg = null;
 2245                  break;
 246               default:
 0247                  throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 248            }
 249         }
 250
 2251         if ( sendBatch )
 252         {
 2253            var batchCount = statement.BatchParameterCount;
 2254            var affectedRowsArray = new Int32[batchCount];
 255            // Send and receive messages asynchronously
 2256            var commandTag = new String[1];
 2257            await
 2258#if NET40
 2259               TaskEx
 2260#else
 2261               Task
 2262#endif
 2263               .WhenAll(
 2264               this.SendMessagesForBatch( statement, typeInfos, stmtName, ioArgs, chunkSize, batchCount ),
 2265               this.ReceiveMessagesForBatch( notices, affectedRowsArray, commandTag )
 2266               );
 2267            current = new BatchCommandExecutionResultImpl(
 2268               commandTag[0],
 2269               new Lazy<SQLException[]>( () => notices?.Select( n => new PgSQLException( n ) )?.ToArray() ),
 2270               affectedRowsArray
 2271               );
 2272         }
 273
 2274         return (current, null);
 2275      }
 276
 277      private async Task SendMessagesForBatch(
 278         SQLStatementBuilderInformation statement,
 279         TypeFunctionalityInformation[] typeInfos,
 280         String statementName,
 281         MessageIOArgs ioArgs,
 282         Int32 chunkSize,
 283         Int32 batchCount
 284         )
 285      {
 2286         var singleRowParamCount = statement.SQLParameterCount;
 287         Int32 max;
 2288         var execMessage = new ExecuteMessage();
 8289         for ( var i = 0; i < batchCount; i = max )
 290         {
 2291            max = Math.Min( batchCount, i + chunkSize );
 16292            for ( var j = i; j < max; ++j )
 293            {
 294               // Send Bind and Execute messages
 295               // TODO reuse BindMessage -> add Reset method.
 6296               await new BindMessage(
 6297                  statement.GetParametersEnumerable( j ),
 6298                  singleRowParamCount,
 6299                  typeInfos,
 6300                  this.DisableBinaryProtocolSend,
 6301                  this.DisableBinaryProtocolReceive,
 6302                  statementName: statementName
 6303                  ).SendMessageAsync( ioArgs, true );
 6304               await execMessage.SendMessageAsync( ioArgs, true );
 305            }
 306
 307            // Now send flush message for backend to start sending results back
 2308            await FrontEndMessageWithNoContent.FLUSH.SendMessageAsync( ioArgs, false );
 309         }
 2310      }
 311
 312      private async Task ReceiveMessagesForBatch(
 313         List<PgSQLError> notices,
 314         Int32[] affectedRows,
 315         String[] commandTag // This is fugly, but other option is to make both ReceiveMessagesForBatch and SendMessages
 316         )
 317      {
 318         // We must allocate new buffer, since the reading will be done concurrently while the writing still performs
 319         // Furthermore, if some error is occurred during sending task, the backend will send error response right away.
 2320         var buffer = new ResizableArray<Byte>( initialSize: 8, exponentialResize: true );
 321
 16322         for ( var i = 0; i < affectedRows.Length; ++i )
 323         {
 6324            var msg = ( await this.ReadMessagesUntilMeaningful( notices, bufferToUse: buffer ) ).Item1;
 6325            if ( msg is MessageWithNoContents nc && msg.Code == BackendMessageCode.BindComplete )
 326            {
 327               // Bind was successul - now read result of execute message
 6328               msg = null;
 12329               while ( msg == null )
 330               {
 331                  Int32 remaining;
 6332                  (msg, remaining) = await this.ReadMessagesUntilMeaningful( notices, bufferToUse: buffer );
 6333                  switch ( msg )
 334                  {
 335                     case CommandComplete cc:
 6336                        Interlocked.Exchange( ref affectedRows[i], cc.AffectedRows ?? 0 );
 6337                        if ( commandTag[0] == null )
 338                        {
 2339                           Interlocked.Exchange( ref commandTag[0], cc.CommandTag );
 340                        }
 2341                        break;
 342                     case DataRowObject dr:
 343                        // Skip thru data
 0344                        await this.Stream.ReadSpecificAmountAsync( buffer.SetCapacityAndReturnArray( remaining ), 0, rem
 345                        // And read more
 0346                        msg = null;
 0347                        break;
 348                     default:
 0349                        throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 350                  }
 351               }
 6352            }
 353            else
 354            {
 0355               throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 356            }
 357         }
 2358      }
 359
 360      protected override async ValueTask<TStatementExecutionSimpleTaskParameter> ExecuteStatementAsPrepared(
 361         SQLStatementBuilderInformation statement,
 362         ReservedForStatement reservedState
 363         )
 364      {
 86365         (var parameterIndices, var typeInfos, var typeIDs) = GetVariablesForExtendedQuerySequence( statement, this.Type
 45366         var ioArgs = this.GetIOArgs();
 367
 368         // First, send the parse message
 47369         await new ParseMessage( statement.SQL, parameterIndices, typeIDs ).SendMessageAsync( ioArgs, true );
 370
 371         // Then send bind message
 46372         var bindMsg = new BindMessage( statement.GetParametersEnumerable(), parameterIndices.Length, typeInfos, this.Di
 45373         await bindMsg.SendMessageAsync( ioArgs, true );
 374
 375         // Then send describe message
 45376         await new DescribeMessage( false ).SendMessageAsync( ioArgs, true );
 377
 378         // Then execute message
 46379         await new ExecuteMessage().SendMessageAsync( ioArgs, true );
 380
 381         // Then flush in order to receive response
 47382         await FrontEndMessageWithNoContent.FLUSH.SendMessageAsync( ioArgs, false );
 383
 384         // Start receiving messages
 47385         BackendMessageObject msg = null;
 47386         SQLStatementExecutionResult current = null;
 47387         Func<ValueTask<(Boolean, SQLStatementExecutionResult)>> moveNext = null;
 47388         RowDescription seenRD = null;
 47389         List<PgSQLError> notices = new List<PgSQLError>();
 232390         while ( msg == null )
 391         {
 188392            msg = ( await this.ReadMessagesUntilMeaningful( notices ) ).Item1;
 393            switch ( msg )
 394            {
 395               case MessageWithNoContents nc:
 93396                  switch ( nc.Code )
 397                  {
 398                     case BackendMessageCode.ParseComplete:
 399                     case BackendMessageCode.BindComplete:
 400                     case BackendMessageCode.NoData:
 401                        // Continue reading messages
 93402                        msg = null;
 93403                        break;
 404                     case BackendMessageCode.EmptyQueryResponse:
 405                        // The statement does not produce any data, we are done
 406                        break;
 407                     default:
 0408                        throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 409                  }
 410                  break;
 411               case RowDescription rd:
 412                  // 0..* DataRowObjects incoming...
 46413                  seenRD = rd;
 47414                  msg = null;
 47415                  break;
 416               case DataRowObject dr:
 47417                  var streamArray = new PgSQLDataRowColumn[seenRD.Fields.Length];
 47418                  var mdArray = new PgSQLDataColumnMetaDataImpl[streamArray.Length];
 47419                  PgSQLDataRowColumn prevCol = null;
 186420                  for ( var i = 0; i < streamArray.Length; ++i )
 421                  {
 47422                     var curField = seenRD.Fields[i];
 47423                     var curMD = new PgSQLDataColumnMetaDataImpl( this, curField.DataFormat, curField.dataTypeID, this.T
 47424                     var curStream = new PgSQLDataRowColumn( curMD, i, prevCol, this, reservedState, curField );
 47425                     prevCol = curStream;
 47426                     streamArray[i] = curStream;
 46427                     curStream.Reset( dr );
 47428                     mdArray[i] = curMD;
 429                  }
 46430                  var warningsLazy = LazyFactory.NewReadOnlyResettableLazy<SQLException[]>( () => notices?.Select( n => 
 47431                  var dataRowCurrent = new SQLDataRowImpl(
 47432                        new PgSQLDataRowMetaDataImpl( mdArray ),
 47433                        streamArray,
 47434                        warningsLazy
 47435                        );
 46436                  current = dataRowCurrent;
 54437                  moveNext = async () => await this.MoveNextAsync( reservedState, streamArray, notices, dataRowCurrent, 
 46438                  break;
 439               case CommandComplete cc:
 0440                  if ( seenRD == null )
 441                  {
 0442                     current = new SingleCommandExecutionResultImpl(
 0443                        cc.CommandTag,
 0444                        new Lazy<SQLException[]>( () => notices?.Select( n => new PgSQLException( n ) )?.ToArray() ),
 0445                        cc.AffectedRows ?? 0
 0446                        );
 447                  }
 0448                  break;
 449               default:
 0450                  throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 451            }
 452         }
 453
 46454         return (current, moveNext);
 47455      }
 456
 457      protected override async ValueTask<TStatementExecutionSimpleTaskParameter> ExecuteStatementAsSimple(
 458         SQLStatementBuilderInformation stmt,
 459         ReservedForStatement reservedState
 460         )
 461      {
 462         // Send Query message
 49463         await new QueryMessage( stmt.SQL ).SendMessageAsync( this.GetIOArgs() );
 464
 465         // Then wait for appropriate response
 49466         List<PgSQLError> notices = new List<PgSQLError>();
 49467         Func<ValueTask<(Boolean, SQLStatementExecutionResult)>> drMoveNext = null;
 468
 469         // We have to always set moveNext, since we might be executing arbitrary amount of SQL statements in simple Sta
 49470         Func<ValueTask<(Boolean, SQLStatementExecutionResult)>> moveNext = async () =>
 49471         {
 175472            SQLStatementExecutionResult current = null;
 175473            if ( drMoveNext != null )
 49474            {
 49475               // We are iterating over some query result, check that first.
 122476               var drNext = await drMoveNext();
 122477               if ( drNext.Item1 )
 49478               {
 110479                  current = drNext.Item2;
 110480               }
 49481               else
 49482               {
 61483                  drMoveNext = null;
 49484               }
 49485            }
 49486
 175487            if ( current == null )
 49488            {
 114489               BackendMessageObject msg = null;
 114490               RowDescription seenRD = null;
 226491               while ( msg == null )
 49492               {
 161493                  msg = ( await this.ReadMessagesUntilMeaningful( notices ) ).Item1;
 49494
 49495                  switch ( msg )
 49496                  {
 49497                     case CommandComplete cc:
 53498                        if ( seenRD == null )
 49499                        {
 53500                           current = new SingleCommandExecutionResultImpl(
 53501                              cc.CommandTag,
 53502                              new Lazy<SQLException[]>( () => notices?.Select( n => new PgSQLException( n ) )?.ToArray()
 53503                              cc.AffectedRows ?? 0
 53504                              );
 53505                        }
 49506                        else
 49507                        {
 49508                           // RowDescription followed immediately by CommandComplete -> treat as empty query
 49509                           // Read more
 49510                           msg = null;
 49511                        }
 53512                        seenRD = null;
 53513                        break;
 49514                     case RowDescription rd:
 96515                        seenRD = rd;
 49516                        // Read more (DataRow or CommandComplete)
 96517                        msg = null;
 96518                        break;
 49519                     case DataRowObject dr:
 49520                        // First DataRowObject
 96521                        var streamArray = new PgSQLDataRowColumn[seenRD.Fields.Length];
 96522                        var mdArray = new PgSQLDataColumnMetaDataImpl[streamArray.Length];
 96523                        PgSQLDataRowColumn prevCol = null;
 263524                        for ( var i = 0; i < streamArray.Length; ++i )
 49525                        {
 109526                           var curField = seenRD.Fields[i];
 109527                           var curMD = new PgSQLDataColumnMetaDataImpl( this, curField.DataFormat, curField.dataTypeID, 
 109528                           var curStream = new PgSQLDataRowColumn( curMD, i, prevCol, this, reservedState, curField );
 109529                           prevCol = curStream;
 109530                           streamArray[i] = curStream;
 109531                           curStream.Reset( dr );
 109532                           mdArray[i] = curMD;
 49533                        }
 96534                        var warningsLazy = LazyFactory.NewReadOnlyResettableLazy<SQLException[]>( () => notices?.Select(
 96535                        var dataRowCurrent = new SQLDataRowImpl(
 96536                              new PgSQLDataRowMetaDataImpl( mdArray ),
 96537                              streamArray,
 96538                              warningsLazy
 96539                              );
 96540                        current = dataRowCurrent;
 169541                        drMoveNext = async () => await this.MoveNextAsync( reservedState, streamArray, notices, dataRowC
 96542                        break;
 49543                     case ReadyForQuery rfq:
 63544                        ( (PgReservedForStatement) reservedState ).RFQSeen();
 63545                        break;
 49546                     default:
 49547                        if ( !ReferenceEquals( MessageWithNoContents.EMPTY_QUERY, msg ) )
 49548                        {
 49549                           throw new PgSQLException( "Unrecognized response at this point: " + msg.Code );
 49550                        }
 49551                        // Read more
 49552                        msg = null;
 49553                        break;
 49554                  }
 49555               }
 114556            }
 49557
 175558            return (current != null, current);
 175559         };
 560
 49561         var firstResult = await moveNext();
 562
 49563         return (firstResult.Item1 ? firstResult.Item2 : null, moveNext);
 564
 49565      }
 566
 567      private async Task<(Boolean, SQLStatementExecutionResult)> MoveNextAsync(
 568         ReservedForStatement reservationObject,
 569         PgSQLDataRowColumn[] streams,
 570         List<PgSQLError> notices,
 571         SQLDataRowImpl dataRow,
 572         ReadOnlyResettableLazy<SQLException[]> warningsLazy
 573         )
 574      {
 80575         return await this.UseStreamWithinStatementAsync( reservationObject, async () =>
 80576         {
 80577            // Force read of all columns
 748578            foreach ( var colStream in streams )
 80579            {
 334580               await colStream.SkipBytesAsync( this.Buffer.Array );
 80581            }
 80582
 160583            notices.Clear();
 160584            var msg = ( await this.ReadMessagesUntilMeaningful( notices ) ).Item1;
 160585            var dr = msg as DataRowObject;
 748586            foreach ( var stream in streams )
 80587            {
 334588               stream.Reset( dr );
 80589            }
 80590
 160591            var retVal = dr != null;
 160592            warningsLazy.Reset();
 160593            return (Success: retVal, Item: dataRow);
 160594         } );
 80595      }
 596
 597
 598      public TransactionStatus LastSeenTransactionStatus
 599      {
 600         get
 601         {
 0602            return (TransactionStatus) this._lastSeenTransactionStatus;
 603         }
 604         private set
 605         {
 122606            Interlocked.Exchange( ref this._lastSeenTransactionStatus, (Int32) value );
 122607         }
 608      }
 609
 610      //public Boolean StandardConformingStrings
 611      //{
 612      //   get
 613      //   {
 614      //      return Convert.ToBoolean( this._standardConformingStrings );
 615      //   }
 616      //   set
 617      //   {
 618      //      Interlocked.Exchange( ref this._standardConformingStrings, Convert.ToInt32( value ) );
 619      //   }
 620      //}
 621
 622      protected override async Task PerformDisposeStatementAsync(
 623         ReservedForStatement reservationObject
 624         )
 625      {
 93626         var ioArgs = this.GetIOArgs();
 99627         var pgReserved = (PgReservedForStatement) reservationObject;
 99628         if ( !String.IsNullOrEmpty( pgReserved.StatementName ) )
 629         {
 630            // Need to close our named statement
 2631            await new CloseMessage( true, pgReserved.StatementName ).SendMessageAsync( ioArgs, true );
 632         }
 633
 634         // Simple statement already received RFQ in its MoveNext method
 95635         if ( !pgReserved.IsSimple )
 636         {
 637            // Need to send SYNC
 48638            await FrontEndMessageWithNoContent.SYNC.SendMessageAsync( ioArgs );
 639
 640         }
 641
 642         // TODO The new moveNextEnded parameter could tell that instead of RFQEncountered property, investigate that
 99643         if ( !pgReserved.RFQEncountered )
 644         {
 645            // Then wait for RFQ
 646            // This happens for non-simple statements, or simple statements which cause exception when iterated over.
 647            BackendMessageObject msg;
 648            Int32 remaining;
 165649            while ( ( (msg, remaining) = ( await this.ReadMessagesUntilMeaningful( null, dontThrowExceptions: true ) ) )
 650            {
 80651               if ( remaining > 0 )
 652               {
 0653                  ioArgs.Item4.CurrentMaxCapacity = remaining;
 0654                  await ioArgs.Item2.ReadSpecificAmountAsync( ioArgs.Item4.Array, 0, remaining, ioArgs.Item3 );
 655               }
 656            }
 657         }
 99658      }
 659
 1436660      public BackendABIHelper MessageIOArgs { get; }
 661
 1881662      public ResizableArray<Byte> Buffer { get; }
 663
 1471664      public Stream Stream { get; }
 665
 666#if !NETSTANDARD1_0
 6667      public Socket Socket { get; }
 668#endif
 669
 564670      public ResizableArray<ResettableTransformable<Int32?, Int32>> DataRowColumnSizes { get; }
 671
 46672      public Boolean DisableBinaryProtocolSend { get; }
 49673      public Boolean DisableBinaryProtocolReceive { get; }
 674
 4675      public Queue<NotificationEventArgs> EnqueuedNotifications { get; }
 676
 677      internal async ValueTask<Object> ConvertFromBytes(
 678         Int32 typeID,
 679         DataFormat dataFormat,
 680         EitherOr<ReservedForStatement, Stream> stream,
 681         Int32 byteCount
 682         )
 683      {
 328684         var actualStream = stream.IsFirst ? this.Stream : stream.Second;
 328685         var typeInfo = this.TypeRegistry.TryGetTypeInfo( typeID );
 329686         if ( typeInfo != null )
 687         {
 137688            var limitedStream = StreamFactory.CreateLimitedReader(
 137689                  actualStream,
 137690                  byteCount,
 137691                  this.CurrentCancellationToken,
 137692                  this.Buffer
 137693                  );
 694
 695            try
 696            {
 137697               return await typeInfo.Functionality.ReadBackendValueAsync(
 137698                  dataFormat,
 137699                  typeInfo.DatabaseData,
 137700                  this.MessageIOArgs,
 137701                  limitedStream
 137702                  );
 703            }
 704            finally
 705            {
 706               try
 707               {
 136708                  await limitedStream.SkipThroughRemainingBytes();
 137709               }
 0710               catch
 711               {
 712                  // Ignore this one.
 0713               }
 714
 715            }
 716
 0717         }
 192718         else if ( dataFormat == DataFormat.Text )
 719         {
 720            // Initial type load, or unknown type and format is textual
 192721            await actualStream.ReadSpecificAmountAsync( this.Buffer, 0, byteCount, this.CurrentCancellationToken );
 192722            return this.MessageIOArgs.GetStringWithPool( this.Buffer.Array, 0, byteCount );
 723         }
 724         else
 725         {
 726            // Unknown type, and data format is binary.
 0727            throw new PgSQLException( $"The type ID {typeID} is not known." );
 728         }
 327729      }
 730
 731      internal async ValueTask<(BackendMessageObject, Int32)> ReadMessagesUntilMeaningful(
 732         List<PgSQLError> notices,
 733         Func<Boolean> checkReadForNextMessage = null,
 734         ResizableArray<Byte> bufferToUse = null,
 735         Boolean dontThrowExceptions = false
 736      )
 737      {
 738         Boolean encounteredMeaningful;
 561739         var ioArgs = this.GetIOArgs( bufferToUse );
 740         BackendMessageObject msg;
 741         Int32 remaining;
 742         do
 743         {
 564744            (msg, remaining) = await BackendMessageObject.ReadBackendMessageAsync( ioArgs, this.DataRowColumnSizes );
 563745            switch ( msg )
 746            {
 747               case PgSQLErrorObject errorObject:
 0748                  encounteredMeaningful = false;
 0749                  if ( errorObject.Code == BackendMessageCode.NoticeResponse )
 750                  {
 0751                     if ( notices != null )
 752                     {
 0753                        notices.Add( ( (PgSQLErrorObject) msg ).Error );
 754                     }
 0755                  }
 0756                  else if ( !dontThrowExceptions )
 757                  {
 0758                     throw new PgSQLException( ( (PgSQLErrorObject) msg ).Error );
 759                  }
 760                  break;
 761               case NotificationMessage notification:
 1762                  this.EnqueuedNotifications.Enqueue( notification.Args );
 1763                  encounteredMeaningful = false;
 1764                  break;
 765               case ParameterStatus ps:
 0766                  this._serverParameters[ps.Name] = ps.Value;
 0767                  encounteredMeaningful = false;
 0768                  break;
 769               default:
 770                  {
 561771                     if ( msg is ReadyForQuery rfq )
 772                     {
 98773                        this.LastSeenTransactionStatus = rfq.Status;
 774                     }
 562775                     encounteredMeaningful = true;
 776                     break;
 777                  }
 778
 779            }
 563780         } while ( !encounteredMeaningful && ( checkReadForNextMessage?.Invoke() ?? true ) );
 781
 563782         return (msg, remaining);
 564783      }
 784
 785      public async Task PerformClose( CancellationToken token )
 786      {
 787         // Send termination message
 788         // Don't use this.CurrentCancellationToken, since one-time pool has already reset the token.
 789         // Furthermore, we might come here from other entrypoints than connection pool's UseConnection (e.g. when dispo
 24790         await FrontEndMessageWithNoContent.TERMINATION.SendMessageAsync( this.GetIOArgs( tokenToUse: token ) );
 24791      }
 792
 793#if !NETSTANDARD1_0
 794      private Boolean SocketHasDataPending()
 795      {
 3796         var socket = this.Socket;
 3797         return socket.Available > 0 || socket.Poll( 1, SelectMode.SelectRead ) || socket.Available > 0;
 798      }
 799#endif
 800
 801      public async ValueTask<NotificationEventArgs[]> CheckNotificationsAsync()
 802      {
 803         // TODO this could be optimized a little, if we notice EnqueuedNotifications.Count > 0, then just don't read fr
 2804         NotificationEventArgs[] args = null;
 805
 806         NotificationEventArgs[] GetEnqueuedNotifications()
 807         {
 0808            var enqueued = this.EnqueuedNotifications.ToArray();
 0809            this.EnqueuedNotifications.Clear();
 0810            return enqueued;
 811         }
 812
 813#if !NETSTANDARD1_0
 2814         var socket = this.Socket;
 2815         if ( socket == null )
 816         {
 817#endif
 818            // Just do "SELECT 1"; to get any notifications
 0819            var enumerable = this.PrepareStatementForExecution( this.VendorFunctionality.CreateStatementBuilder( "SELECT
 0820               .AsObservable();
 821            // Use GetEnqueuedNotifications while we are still inside statement reservation region, by registering to Be
 0822            enumerable.BeforeEnumerationEnd += ( eArgs ) => args = GetEnqueuedNotifications();
 0823            await enumerable.EnumerateAsync();
 824#if !NETSTANDARD1_0
 0825         }
 826         else
 827         {
 828            // First, check from the socket that we have any data pending
 829
 2830            var hasDataPending = this.SocketHasDataPending();
 2831            if ( hasDataPending || this.EnqueuedNotifications.Count > 0 )
 832            {
 833               // There is pending data
 834               // We always must use UseStreamOutsideStatementAsync method, since modifying this.EnqueuedNotifications o
 0835               await this.UseStreamOutsideStatementAsync( async () =>
 0836               {
 0837                  // If we call "ReadMessagesUntilMeaningful" with no socket data pending, we will never break free of l
 0838                  if ( hasDataPending )
 0839                  {
 0840                     await this.ReadMessagesUntilMeaningful(
 0841                        null,
 0842                        this.SocketHasDataPending
 0843                        );
 0844                  }
 0845                  args = GetEnqueuedNotifications();
 0846                  return false;
 0847               } );
 848            }
 849         }
 850#endif
 851
 2852         return args ?? Empty<NotificationEventArgs>.Array;
 853
 2854      }
 855
 856
 857      public IAsyncEnumerable<NotificationEventArgs> ListenToNotificationsAsync()
 858      {
 859#if !NETSTANDARD1_0
 1860         if ( this.Socket == null )
 861         {
 862#else
 863         throw new NotSupportedException( "No socket available for this method." );
 864#endif
 865#if !NETSTANDARD1_0
 866         }
 867
 1868         var enqueued = this.EnqueuedNotifications;
 869         Boolean KeepReadingMore()
 870         {
 1871            return enqueued.Count <= 0 || ( enqueued.Count <= 1000 && this.SocketHasDataPending() );
 872         }
 873
 874         async Task PerformReadForNotifications()
 875         {
 1876            if ( enqueued.Count <= 0 )
 877            {
 1878               await this.ReadMessagesUntilMeaningful( null, KeepReadingMore );
 879            }
 1880         }
 881
 1882         return AsyncEnumerationFactory.CreateStatefulWrappingEnumerable( () =>
 1883         {
 2884            PgReservedForStatement reservation = null;
 2885            return AsyncEnumerationFactory.CreateWrappingStartInfo(
 2886               async () =>
 2887               {
 3888                  if ( reservation == null )
 2889                  {
 3890                     reservation = new PgReservedForStatement(
 3891#if DEBUG
 3892                        null,
 3893#endif
 3894                        true,
 3895                        null
 3896                        );
 3897                     reservation.RFQSeen();
 3898                     await this.UseStreamOutsideStatementAsync( reservation, PerformReadForNotifications, false, true );
 3899                  }
 2900                  else
 2901                  {
 2902                     await this.UseStreamWithinStatementAsync( reservation, PerformReadForNotifications, true );
 2903                  }
 2904
 3905                  return enqueued.Count > 0;
 3906               },
 2907               ( out Boolean success ) =>
 2908               {
 3909                  success = enqueued.Count > 0;
 3910                  return success ? enqueued.Dequeue() : default;
 2911               },
 2912               () =>
 2913               {
 3914                  return this.DisposeStatementAsync( reservation );
 2915               }
 2916               );
 1917         }, this.AsyncProvider );
 918#endif
 919      }
 920
 921      public static async Task<(PostgreSQLProtocol Protocol, List<PgSQLError> notices)> PerformStartup(
 922         PgSQLConnectionVendorFunctionality vendorFunctionality,
 923         PgSQLConnectionCreationInfo creationInfo,
 924         CancellationToken token,
 925         Stream stream,
 926         BackendABIHelper abiHelper,
 927         ResizableArray<Byte> buffer
 928#if !NETSTANDARD1_0
 929         , Socket socket
 930#endif
 931         )
 932      {
 24933         var initData = creationInfo?.CreationData?.Initialization ?? throw new PgSQLException( "Please specify initiali
 24934         var startupInfo = await DoConnectionInitialization(
 24935            creationInfo,
 24936            (abiHelper, stream, token, buffer)
 24937            );
 24938         var protoConfig = initData?.Protocol;
 24939         var retVal = (
 24940            new PostgreSQLProtocol(
 24941               vendorFunctionality,
 24942               protoConfig?.DisableBinaryProtocolSend ?? false,
 24943               protoConfig?.DisableBinaryProtocolReceive ?? false,
 24944               abiHelper,
 24945               stream,
 24946               buffer,
 24947               startupInfo.ServerParameters,
 24948               startupInfo.TransactionStatus,
 24949               startupInfo.backendProcessID ?? 0
 24950#if !NETSTANDARD1_0
 24951               , socket
 24952#endif
 24953            ),
 24954            startupInfo.Notices ?? new List<PgSQLError>()
 24955            );
 956
 24957         await retVal.Item1.ReadTypesFromServer( protoConfig?.ForceTypeIDLoad ?? false, token );
 958
 24959         return retVal;
 24960      }
 961
 962      internal const String SERVER_PARAMETER_DATABASE = "database";
 963
 964      private static async Task<(IDictionary<String, String> ServerParameters, Int32? backendProcessID, Int32? backendKe
 965         PgSQLConnectionCreationInfo creationInfo,
 966         MessageIOArgs ioArgs
 967         )
 968      {
 24969         var dbConfig = creationInfo?.CreationData?.Initialization?.Database ?? throw new ArgumentException( "Please spe
 24970         var authConfig = creationInfo?.CreationData?.Initialization?.Authentication ?? throw new ArgumentException( "Pl
 971
 24972         var encoding = ioArgs.Item1.Encoding.Encoding;
 24973         var username = authConfig.Username ?? throw new ArgumentException( "Please specify username in authentication c
 24974         var parameters = new Dictionary<String, String>()
 24975         {
 24976            { SERVER_PARAMETER_DATABASE, dbConfig.Name ?? throw new ArgumentException("Please specify database name in d
 24977            { "user",username },
 24978            { "DateStyle", "ISO" },
 24979            { "client_encoding", encoding.WebName  },
 24980            { "extra_float_digits", "2" },
 24981            { "lc_monetary", "C" }
 24982         };
 24983         var sp = dbConfig.SearchPath;
 24984         if ( !String.IsNullOrEmpty( sp ) )
 985         {
 0986            parameters.Add( "search_path", sp );
 987         }
 988
 24989         await new StartupMessage( 3 << 16, parameters ).SendMessageAsync( ioArgs );
 990
 991         BackendMessageObject msg;
 24992         List<PgSQLError> notices = null;
 24993         Int32? backendProcessID = null;
 24994         Int32? backendKeyData = null;
 24995         TransactionStatus tStatus = 0;
 24996         Object saslState = null;
 997         try
 998         {
 999            do
 1000            {
 1001               Int32 ignored;
 3641002               (msg, ignored) = await BackendMessageObject.ReadBackendMessageAsync( ioArgs, null );
 3641003               switch ( msg )
 1004               {
 1005                  case ParameterStatus ps:
 2641006                     parameters[ps.Name] = ps.Value;
 2641007                     break;
 1008                  case AuthenticationResponse auth:
 521009                     var newSaslState = await ProcessAuth(
 521010                        creationInfo,
 521011                        username,
 521012                        ioArgs,
 521013                        auth,
 521014                        saslState
 521015                        );
 521016                     if ( newSaslState != null )
 1017                     {
 81018                        saslState = newSaslState;
 1019                     }
 81020                     break;
 1021                  case PgSQLErrorObject error:
 01022                     if ( error.Code == BackendMessageCode.NoticeResponse )
 1023                     {
 01024                        if ( notices == null )
 1025                        {
 01026                           notices = new List<PgSQLError>();
 1027                        }
 01028                        notices.Add( error.Error );
 01029                     }
 1030                     else
 1031                     {
 01032                        throw new PgSQLException( error.Error );
 1033                     }
 1034                     break;
 1035                  case BackendKeyData key:
 241036                     backendProcessID = key.ProcessID;
 241037                     backendKeyData = key.Key;
 241038                     break;
 1039                  case ReadyForQuery rfq:
 241040                     tStatus = rfq.Status;
 1041                     break;
 1042               }
 3641043            } while ( msg.Code != BackendMessageCode.ReadyForQuery );
 241044         }
 1045         finally
 1046         {
 241047            DisposeSASLState( saslState );
 1048         }
 241049         return (parameters, backendProcessID, backendKeyData, notices, tStatus);
 241050      }
 1051
 1052      private static async Task<Object> ProcessAuth(
 1053         PgSQLConnectionCreationInfo creationInfo,
 1054         String username,
 1055         MessageIOArgs ioArgs,
 1056         AuthenticationResponse msg,
 1057         Object saslState
 1058         )
 1059      {
 521060         var authType = msg.RequestType;
 521061         var initData = creationInfo.CreationData.Initialization.Database;
 521062         switch ( authType )
 1063         {
 1064            case AuthenticationResponse.AuthenticationRequestType.AuthenticationClearTextPassword:
 01065               await new PasswordMessage( GetPasswordBytes( creationInfo, ioArgs ) ).SendMessageAsync( ioArgs );
 01066               break;
 1067            case AuthenticationResponse.AuthenticationRequestType.AuthenticationMD5Password:
 221068               await HandleMD5Authentication( ioArgs, msg, username, GetPasswordBytes( creationInfo, ioArgs ) ).SendMess
 221069               break;
 1070            case AuthenticationResponse.AuthenticationRequestType.AuthenticationOk:
 1071               // Nothing to do
 1072               break;
 1073            case AuthenticationResponse.AuthenticationRequestType.AuthenticationSASL:
 21074               var saslResult = ( HandleSASLAuthentication_Start( creationInfo, ioArgs, username, msg ) );
 21075               saslState = saslResult.Item2;
 21076               await ( saslResult.Item1 ?? throw new PgSQLException( "Authentication failed." ) ).SendMessageAsync( ioAr
 21077               break;
 1078            case AuthenticationResponse.AuthenticationRequestType.AuthenticationSASLContinue:
 21079               await ( HandleSASLAuthentication_Continue( ioArgs, msg, saslState ) ?? throw new PgSQLException( "Authent
 21080               break;
 1081            case AuthenticationResponse.AuthenticationRequestType.AuthenticationSASLFinal:
 21082               HandleSASLAuthentication_Final( creationInfo, ioArgs, msg, saslState );
 21083               break;
 1084            default:
 01085               throw new PgSQLException( $"Authentication kind {authType} is not support." );
 1086         }
 1087
 521088         return saslState;
 521089      }
 1090
 1091      private static Byte[] GetPasswordBytes(
 1092         PgSQLConnectionCreationInfo creationInfo,
 1093         MessageIOArgs ioArgs
 1094         )
 1095      {
 221096         var authConfig = creationInfo.CreationData.Initialization.Authentication;
 221097         var encoding = ioArgs.Item1.Encoding.Encoding;
 221098         return ( String.Equals( PgSQLAuthenticationConfiguration.PasswordByteEncoding.WebName, encoding.WebName ) ?
 221099            authConfig.PasswordBytes :
 221100            encoding.GetBytes( authConfig.Password ) ) ?? throw new PgSQLException( "Backend requested password, but it 
 1101      }
 1102
 1103      // Having this in separate method also won't force load of UtilPack.Cryptography assemblies if other than MD5/SASL
 1104      private static PasswordMessage HandleMD5Authentication(
 1105         MessageIOArgs ioArgs,
 1106         AuthenticationResponse msg,
 1107         String username,
 1108         Byte[] pw
 1109         )
 1110      {
 221111         var buffer = ioArgs.Item4;
 221112         var helper = ioArgs.Item1;
 1113
 221114         if ( pw == null )
 1115         {
 01116            throw new PgSQLException( "Backend requested password, but it was not supplied." );
 1117         }
 221118         using ( var md5 = new FluentCryptography.Digest.MD5() )
 1119         {
 1120            // Extract server salt before using args.Buffer
 1121
 221122            var serverSalt = buffer.Array.CreateArrayCopy( msg.AdditionalDataInfo.offset, msg.AdditionalDataInfo.count )
 1123
 1124            // Hash password with username as salt
 221125            var prehashLength = helper.Encoding.Encoding.GetByteCount( username ) + pw.Length;
 221126            buffer.CurrentMaxCapacity = prehashLength;
 221127            var idx = 0;
 221128            pw.CopyTo( buffer.Array, ref idx, 0, pw.Length );
 221129            helper.Encoding.Encoding.GetBytes( username, 0, username.Length, buffer.Array, pw.Length );
 221130            var hash = md5.ComputeDigest( buffer.Array, 0, prehashLength );
 1131
 1132            // Write hash as hexadecimal string
 221133            buffer.CurrentMaxCapacity = hash.Length * 2 * helper.Encoding.BytesPerASCIICharacter;
 221134            idx = 0;
 7481135            foreach ( var hashByte in hash )
 1136            {
 3521137               helper.Encoding.WriteHexDecimal( buffer.Array, ref idx, hashByte );
 1138            }
 1139
 1140            // Hash result again with server-provided salt
 221141            buffer.CurrentMaxCapacity += serverSalt.Length;
 221142            var dummy = 0;
 221143            serverSalt.CopyTo( buffer.Array, ref dummy, idx, serverSalt.Length );
 221144            hash = md5.ComputeDigest( buffer.Array, 0, idx + serverSalt.Length );
 1145
 1146            // Send back string "md5" followed by hexadecimal hash value
 221147            buffer.CurrentMaxCapacity = 3 * helper.Encoding.BytesPerASCIICharacter + hash.Length * 2 * helper.Encoding.B
 221148            idx = 0;
 221149            var array = buffer.Array;
 221150            helper.Encoding
 221151               .WriteASCIIByte( array, ref idx, (Byte) 'm' )
 221152               .WriteASCIIByte( array, ref idx, (Byte) 'd' )
 221153               .WriteASCIIByte( array, ref idx, (Byte) '5' );
 7481154            foreach ( var hashByte in hash )
 1155            {
 3521156               helper.Encoding.WriteHexDecimal( array, ref idx, hashByte );
 1157            }
 1158
 221159            var retValArray = new Byte[idx + 1]; // Remember string-terminating zero
 221160            dummy = 0;
 221161            array.CopyTo( retValArray, ref dummy, 0, idx );
 221162            return new PasswordMessage( retValArray );
 1163         }
 1164
 1165
 221166      }
 1167
 1168      private static (PasswordMessage, Object) HandleSASLAuthentication_Start(
 1169         PgSQLConnectionCreationInfo creationInfo,
 1170         MessageIOArgs ioArgs,
 1171         String username,
 1172         AuthenticationResponse msg
 1173         )
 1174      {
 21175         var idx = msg.AdditionalDataInfo.offset;
 21176         var count = msg.AdditionalDataInfo.count;
 21177         var buffer = ioArgs.Item4;
 61178         while ( count > 0 && buffer.Array[idx + count - 1] == 0 )
 1179         {
 41180            --count;
 1181         }
 21182         var protocolEncoding = ioArgs.Item1.Encoding;
 21183         var authSchemes = protocolEncoding.Encoding.GetString( buffer.Array, idx, count );
 1184
 21185         var mechanismInfo = creationInfo.CreateSASLMechanism?.Invoke( authSchemes ) ?? throw new PgSQLException( "Faile
 21186         var mechanism = mechanismInfo.Item1 ?? throw new PgSQLException( "Failed to provide SASL mechanism." );
 21187         var mechanismName = mechanismInfo.Item2 ?? throw new PgSQLException( "Failed to provide SASL mechanism name." )
 21188         var authConfig = creationInfo.CreationData.Initialization.Authentication;
 21189         var pwDigest = authConfig.PasswordDigest;
 21190         var credentials = pwDigest.IsNullOrEmpty() ?
 21191            new SASLCredentialsSCRAMForClient( username, authConfig.Password ) :
 21192            new SASLCredentialsSCRAMForClient( username, pwDigest );
 21193         var writeBuffer = new ResizableArray<Byte>();
 21194         var saslEncoding = new UTF8Encoding( false, true ).CreateDefaultEncodingInfo();
 21195         var challengeResult = mechanism.ChallengeAsync( credentials.CreateChallengeArguments(
 21196            Empty<Byte>.Array,
 21197            -1,
 21198            -1,
 21199            writeBuffer,
 21200            0,
 21201            saslEncoding
 21202            ) ).GetResultForceSynchronous();
 1203
 1204         (PasswordMessage, Object) retVal;
 21205         if ( !challengeResult.IsFirst || challengeResult.First.Item2 != SASLChallengeResult.MoreToCome )
 1206         {
 01207            retVal = default;
 01208         }
 1209         else
 1210         {
 1211            // SASL initial response is: null-terminated string for mechanism name, length of initial response, and init
 21212            var bytesWritten = challengeResult.First.Item1;
 21213            var pwArray = new Byte[
 21214               protocolEncoding.Encoding.GetByteCount( mechanismName ) + protocolEncoding.BytesPerASCIICharacter
 21215               + sizeof( Int32 )
 21216               + bytesWritten
 21217               ];
 21218            idx = protocolEncoding.Encoding.GetBytes( mechanismName, 0, mechanismName.Length, pwArray, 0 ) + 1;
 21219            pwArray.WritePgInt32( ref idx, bytesWritten );
 21220            var dummy = 0;
 21221            writeBuffer.Array.CopyTo( pwArray, ref dummy, idx, bytesWritten );
 1222
 21223            retVal = (
 21224               new PasswordMessage( pwArray ),
 21225               new TSASLAuthState( mechanism, credentials, writeBuffer, saslEncoding )
 21226               );
 1227         }
 1228
 21229         return retVal;
 1230
 1231      }
 1232
 1233      private static PasswordMessage HandleSASLAuthentication_Continue(
 1234         MessageIOArgs ioArgs,
 1235         AuthenticationResponse msg,
 1236         Object state
 1237         )
 1238      {
 21239         var challengeResult = HandleSASLAuthentication_ContinueOrFinal( ioArgs, msg, state );
 1240         PasswordMessage retVal;
 21241         if ( challengeResult.IsSecond || challengeResult.First.Item2 != SASLChallengeResult.MoreToCome )
 1242         {
 01243            retVal = default;
 01244         }
 1245         else
 1246         {
 1247            // Responses are password messages with whole SASL message as content
 21248            retVal = new PasswordMessage( ( (TSASLAuthState) state ).Item3.Array.CreateArrayCopy( 0, challengeResult.Fir
 1249         }
 1250
 21251         return retVal;
 1252      }
 1253
 1254      private static void HandleSASLAuthentication_Final(
 1255         PgSQLConnectionCreationInfo creationInfo,
 1256         MessageIOArgs ioArgs,
 1257         AuthenticationResponse msg,
 1258         Object state
 1259         )
 1260      {
 21261         var challengeResult = HandleSASLAuthentication_ContinueOrFinal( ioArgs, msg, state );
 21262         if ( challengeResult.IsSecond || challengeResult.First.Item2 != SASLChallengeResult.Completed )
 1263         {
 01264            throw new PgSQLException( "Authentication failed." );
 1265         }
 1266         else
 1267         {
 1268            try
 1269            {
 21270               creationInfo.OnSASLSCRAMSuccess?.Invoke( ( (TSASLAuthState) state ).Item2.PasswordDigest );
 21271            }
 01272            catch
 1273            {
 1274               // Ignore...
 01275            }
 1276
 21277            DisposeSASLState( state );
 1278         }
 21279      }
 1280
 1281      private static EitherOr<(Int32, SASLChallengeResult), Int32> HandleSASLAuthentication_ContinueOrFinal(
 1282         MessageIOArgs ioArgs,
 1283         AuthenticationResponse msg,
 1284         Object state
 1285         )
 1286      {
 41287         var idx = msg.AdditionalDataInfo.offset;
 41288         var count = msg.AdditionalDataInfo.count;
 41289         var buffer = ioArgs.Item4;
 1290
 41291         var saslState = (TSASLAuthState) state;
 41292         return saslState.Item1.ChallengeAsync( saslState.Item2.CreateChallengeArguments(
 41293            buffer.Array,
 41294            idx,
 41295            count,
 41296            saslState.Item3,
 41297            0,
 41298            saslState.Item4
 41299            ) ).GetResultForceSynchronous();
 1300      }
 1301
 1302      private static void DisposeSASLState( Object state )
 1303      {
 261304         if ( state is TSASLAuthState saslState )
 1305         {
 41306            saslState.Item1?.DisposeSafely();
 41307            saslState.Item3?.Array?.Clear();
 1308         }
 261309      }
 1310
 1311      internal class PgReservedForStatement : ReservedForStatement
 1312      {
 1313         private Int32 _rfqEncountered;
 1314
 941315         public PgReservedForStatement(
 941316#if DEBUG
 941317         Object statement,
 941318#endif
 941319         Boolean isSimple,
 941320            String statementName
 941321            )
 1322#if DEBUG
 1323         : base( statement )
 1324#endif
 1325         {
 961326            this.IsSimple = isSimple;
 981327            this.StatementName = statementName;
 961328            this._rfqEncountered = Convert.ToInt32( false );
 971329         }
 1330
 961331         public Boolean IsSimple { get; }
 1332
 971333         public String StatementName { get; }
 1334
 991335         public Boolean RFQEncountered => Convert.ToBoolean( this._rfqEncountered );
 1336
 1337         public void RFQSeen()
 1338         {
 151339            Interlocked.Exchange( ref this._rfqEncountered, Convert.ToInt32( true ) );
 151340         }
 1341      }
 1342
 1343   }
 1344
 1345   // TODO move to utilpack
 1346   internal static class E_TODO
 1347   {
 1348      public static T GetResultForceSynchronous<T>( this ValueTask<T> task )
 1349      {
 1350         return task.IsCompleted ? task.Result : throw new InvalidOperationException( "ValueTask is not completed when i
 1351      }
 1352   }
 1353}

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Types.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using CBAM.SQL.Implementation;
 19using System;
 20using System.Collections.Generic;
 21using System.Reflection;
 22using System.IO;
 23using System.Linq;
 24using System.Text;
 25using System.Threading;
 26using System.Threading.Tasks;
 27using UtilPack;
 28using TStaticTypeCacheValue = System.Collections.Generic.IDictionary<System.String, CBAM.SQL.PostgreSQL.PgSQLTypeDatabas
 29using TStaticTypeCache = System.Collections.Generic.IDictionary<System.Version, System.Collections.Generic.IDictionary<S
 30using TextBackendSizeInfo = UtilPack.EitherOr<System.ValueTuple<System.Int32, System.Object>, System.String>;
 31using TypeFunctionalityInfo = System.ValueTuple<System.ValueTuple<System.Type, CBAM.SQL.PostgreSQL.PgSQLTypeFunctionalit
 32using CBAM.SQL.PostgreSQL;
 33
 34namespace CBAM.SQL.PostgreSQL.Implementation
 35{
 36   using TSyncTextualSizeInfo = ValueTuple<Int32, String>;
 37
 38   internal sealed partial class PostgreSQLProtocol
 39   {
 40      private static readonly TStaticTypeCache _StaticTypeCache;
 41      private static readonly IDictionary<String, TypeFunctionalityInfo> _DefaultTypes;
 42
 43      static PostgreSQLProtocol()
 44      {
 145         var typeInfo = PostgreSQLProtocol.CreateDefaultTypesInfos()
 3546            .ToDictionary( tInfo => tInfo.Item1, tInfo => tInfo );
 47
 148         _DefaultTypes = new Dictionary<String, TypeFunctionalityInfo>()
 149         {
 150            { "text", (typeInfo[typeof(String)], true) },
 151            { "void", (typeInfo[typeof(String)], false) },
 152            { "char", (typeInfo[typeof(String)], false) },
 153            { "varchar", (typeInfo[typeof(String)], false) },
 154            { "bool", (typeInfo[typeof(Boolean)], true) },
 155            { "int2", (typeInfo[typeof(Int16)], true) },
 156            { "int4", (typeInfo[typeof(Int32)], true) },
 157            { "int8", (typeInfo[typeof(Int64)], true) },
 158            { "oid", (typeInfo[typeof(Int32)], false) },
 159            { "float4", (typeInfo[typeof(Single)], true) },
 160            { "float8", (typeInfo[typeof(Double)], true) },
 161            { "numeric", (typeInfo[typeof(Decimal)], true) },
 162            //{ "inet", typeof(PgSQLInternetAddress) },
 163            //{ "macaddr", typeof(PgSQLMacAddress) },
 164            { "money", (typeInfo[typeof(Decimal)], false) },
 165            { "uuid", (typeInfo[typeof(Guid)], true) },
 166            { "xml", (typeInfo[typeof(System.Xml.Linq.XElement)], true) },
 167            { "interval", (typeInfo[typeof(PgSQLInterval)], true) },
 168            { "date", (typeInfo[typeof(PgSQLDate)], true) },
 169            { "time", (typeInfo[typeof(PgSQLTime)], true) },
 170            { "timetz", (typeInfo[typeof(PgSQLTimeTZ)], true) },
 171            { "timestamp", (typeInfo[typeof(PgSQLTimestamp)], true) },
 172            { "timestamptz", (typeInfo[typeof(PgSQLTimestampTZ)], true) },
 173            { "abstime", (typeInfo[typeof(PgSQLTimestampTZ)], false) },
 174            { "bytea", (typeInfo[typeof(Byte[])], true) }
 175
 176         };
 177         _StaticTypeCache = new Dictionary<Version, TStaticTypeCacheValue>();
 178      }
 79
 80      private async Task ReadTypesFromServer( Boolean force, CancellationToken token )
 81      {
 2482         var serverVersion = this._serverVersion;
 2483         var typeCache = _StaticTypeCache;
 2484         if ( force || !typeCache.TryGetValue( serverVersion, out TStaticTypeCacheValue types ) )
 85         {
 186            this.CurrentCancellationToken = token;
 87            try
 88            {
 189               types = await this.TypeRegistry.ReadTypeDataFromServer( _DefaultTypes.Keys );
 190            }
 91            finally
 92            {
 193               this.ResetCancellationToken();
 94            }
 95
 196            lock ( typeCache )
 97            {
 198               typeCache[serverVersion] = types;
 199            }
 100         }
 101
 24102         this.TypeRegistry.AssignTypeData(
 24103            types,
 1104104            tName => _DefaultTypes[tName].Item1.Item1,
 24105            tuple =>
 24106            {
 576107               var valTuple = _DefaultTypes[tuple.DBTypeName];
 576108               return new TypeFunctionalityCreationResult( valTuple.Item1.Item2, valTuple.Item2 );
 24109            } );
 24110      }
 111
 112      private static IEnumerable<(Type, PgSQLTypeFunctionality)> CreateDefaultTypesInfos()
 113      {
 114         // Assumes that string is non-empty
 115         Boolean ArrayElementStringNeedsQuotesSingleDelimiter( PgSQLTypeDatabaseData boundData, String value )
 116         {
 16117            Boolean needsQuotes = false;
 16118            var delimChar = boundData.ArrayDelimiter[0];
 84119            for ( var i = 0; i < value.Length && !needsQuotes; ++i )
 120            {
 26121               var c = value[i];
 26122               switch ( c )
 123               {
 124                  case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 125                  case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 126                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 127                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 0128                     needsQuotes = true;
 0129                     break;
 130                  default:
 26131                     if ( c == delimChar || Char.IsWhiteSpace( c ) )
 132                     {
 1133                        needsQuotes = true;
 134                     }
 135                     break;
 136               }
 137            }
 138
 16139            return needsQuotes;
 140         }
 141
 142         Boolean ArrayElementStringNeedsQuotesMultiDelimiter( PgSQLTypeDatabaseData boundData, String value )
 143         {
 0144            var needsQuotes = value.IndexOf( boundData.ArrayDelimiter ) >= 0;
 0145            if ( !needsQuotes )
 146            {
 0147               for ( var i = 0; i < value.Length && !needsQuotes; ++i )
 148               {
 0149                  var c = value[i];
 0150                  switch ( c )
 151                  {
 152                     case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 153                     case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 154                     case PgSQLTypeFunctionalityForArrays.ESCAPE:
 155                     case PgSQLTypeFunctionalityForArrays.QUOTE:
 0156                        needsQuotes = true;
 0157                        break;
 158                     default:
 0159                        if ( Char.IsWhiteSpace( c ) )
 160                        {
 0161                           needsQuotes = true;
 162                        }
 163                        break;
 164                  }
 165               }
 166            }
 0167            return needsQuotes;
 168         }
 169
 170         Int32 CalculateArrayElementStringSizeSingleDelimiter( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, 
 171         {
 2172            var asciiByteSize = encoding.BytesPerASCIICharacter;
 2173            var retVal = 2 * asciiByteSize + encoding.Encoding.GetByteCount( value );
 2174            var delimChar = boundData.ArrayDelimiter[0];
 20175            for ( var i = 0; i < value.Length; ++i )
 176            {
 8177               var c = value[i];
 8178               switch ( c )
 179               {
 180                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 181                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 182                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 183                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 1184                     retVal += asciiByteSize;
 185                     break;
 186                     //default:
 187                     //   if ( delimChar == c || Char.IsWhiteSpace( c ) )
 188                     //   {
 189                     //      retVal += asciiByteSize;
 190                     //   }
 191                     //   break;
 192               }
 193            }
 2194            return retVal;
 195         }
 196
 197         Int32 CalculateArrayElementStringSizeMultiDelimiter( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, S
 198         {
 0199            var asciiByteSize = encoding.BytesPerASCIICharacter;
 0200            var retVal = 2 * asciiByteSize + encoding.Encoding.GetByteCount( value ) + asciiByteSize * value.CountOccurr
 0201            for ( var i = 0; i < value.Length; ++i )
 202            {
 0203               var c = value[i];
 0204               switch ( c )
 205               {
 206                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 207                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 208                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 209                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 0210                     retVal += asciiByteSize;
 211                     break;
 212                     //default:
 213                     //   if ( Char.IsWhiteSpace( c ) )
 214                     //   {
 215                     //      retVal += asciiByteSize;
 216                     //   }
 217                     //   break;
 218               }
 219            }
 0220            return retVal;
 221         }
 222
 223         void WriteArrayElementStringSingleDelimiter( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, Byte[] a
 224         {
 2225            var prevIdx = 0;
 2226            var delimChar = boundData.ArrayDelimiter[0];
 2227            var encoding = helper.Encoding.Encoding;
 20228            for ( var i = 0; i < value.Length; ++i )
 229            {
 8230               var c = value[i];
 8231               switch ( c )
 232               {
 233                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 234                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 235                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 236                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 1237                     offset += encoding.GetBytes( value, prevIdx, i - prevIdx, array, offset );
 1238                     helper.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.ESCAPE );
 1239                     prevIdx = i;
 240                     break;
 241                  default:
 242                     //if ( delimChar == c || Char.IsWhiteSpace( c ) )
 243                     //{
 244                     //   arrayIdx += encoding.GetBytes( value, prevIdx, i - prevIdx, array, arrayIdx );
 245                     //   helper.Encoding.WriteASCIIByte( array, ref arrayIdx, (Byte) PgSQLTypeFunctionalityForArrays.ES
 246                     //   prevIdx = i;
 247                     //}
 248                     break;
 249               }
 250            }
 251
 252            // Last chunk
 2253            offset += encoding.GetBytes( value, prevIdx, value.Length - prevIdx, array, offset );
 2254         }
 255
 256         void WriteArrayElementStringMultiDelimiter( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, Byte[] ar
 257         {
 0258            var prevIdx = 0;
 0259            var encoding = helper.Encoding.Encoding;
 0260            var delimCharStart = boundData.ArrayDelimiter[0];
 0261            var delimCharEnd = boundData.ArrayDelimiter[1];
 0262            for ( var i = 0; i < value.Length; ++i )
 263            {
 0264               var c = value[i];
 0265               switch ( c )
 266               {
 267                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 268                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 269                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 270                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 0271                     offset += encoding.GetBytes( value, prevIdx, i - prevIdx, array, offset );
 0272                     helper.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.ESCAPE );
 0273                     prevIdx = i;
 274                     break;
 275                     //default:
 276                     //   var isDelimStart = c == delimCharStart && i < value.Length - 1 && value[i + 1] == delimCharEnd
 277                     //   if ( isDelimStart || Char.IsWhiteSpace( c ) )
 278                     //   {
 279                     //      arrayIdx += encoding.GetBytes( value, prevIdx, i - prevIdx, array, arrayIdx );
 280                     //      helper.Encoding.WriteASCIIByte( array, ref arrayIdx, (Byte) PgSQLTypeFunctionalityForArrays
 281                     //      prevIdx = i;
 282                     //      if ( isDelimStart )
 283                     //      {
 284                     //         ++i;
 285                     //      }
 286                     //   }
 287                     //   break;
 288               }
 289            }
 290
 291            // Last chunk
 0292            offset += encoding.GetBytes( value, prevIdx, value.Length - prevIdx, array, offset );
 0293         }
 294
 295
 296         // String
 1297         yield return CreateSingleBodyInfoWithType(
 1298         ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1299            {
 103300               return args.GetStringWithPool( array, offset, count );
 1301            },
 1302            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1303            {
 1304               return args.GetStringWithPool( array, offset, count );
 1305            },
 1306            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, String value, Boolean isArrayElement ) =>
 1307            {
 1308               EitherOr<Int32, String> retVal;
 18309               if ( isArrayElement )
 1310               {
 1311                  // From https://www.postgresql.org/docs/current/static/arrays.html :
 1312                  // The array output routine will put double quotes around element values if they are empty strings, co
 1313                  // Assumes that value is non-empty
 18314                  var isNullString = String.Equals( value, "null", StringComparison.OrdinalIgnoreCase );
 18315                  var isSingleDelimiter = boundData.ArrayDelimiter.Length == 1;
 18316                  if (
 18317                     value.Length == 0
 18318                     || isNullString
 18319                     || ( isSingleDelimiter ? ArrayElementStringNeedsQuotesSingleDelimiter( boundData, value ) : ArrayEl
 18320                  )
 1321                  {
 3322                     retVal = isNullString ?
 3323                        6 * encoding.BytesPerASCIICharacter :
 3324                        ( isSingleDelimiter ?
 3325                           CalculateArrayElementStringSizeSingleDelimiter( boundData, encoding, value ) :
 3326                           CalculateArrayElementStringSizeMultiDelimiter( boundData, encoding, value )
 3327                        );
 3328                  }
 1329                  else
 1330                  {
 1331                     // We can serialize without quotes
 16332                     retVal = value;
 1333                  }
 1334
 16335               }
 1336               else
 1337               {
 1338                  retVal = value;
 1339               }
 1340
 18341               return retVal;
 1342            },
 1343            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, String value, Boolean isArrayElement ) =>
 1344            {
 55345               return encoding.Encoding.GetByteCount( value );
 1346            },
 1347            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, String value, TSyncTex
 1348            {
 18349               if ( sizeInfo.Item2 == null )
 1350               {
 1351                  // Because of how text format size is calculated, and because we are handling string type, we will com
 1352
 1353                  // Write starting quote
 3354                  args.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 1355
 1356                  // Write content, and escape any double quotes and backslashes
 3357                  if ( boundData.ArrayDelimiter.Length == 1 )
 1358                  {
 3359                     WriteArrayElementStringSingleDelimiter( boundData, args, array, value, ref offset );
 3360                  }
 1361                  else
 1362                  {
 1363                     WriteArrayElementStringMultiDelimiter( boundData, args, array, value, ref offset );
 1364                  }
 1365
 1366                  // Write ending quote
 3367                  args.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 3368               }
 1369               else
 1370               {
 16371                  offset += args.Encoding.Encoding.GetBytes( value, 0, value.Length, array, offset );
 1372               }
 1373               System.Diagnostics.Debug.Assert( offset == sizeInfo.Item1 );
 16374            },
 1375            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, String value, Int32 si
 1376            {
 51377               args.Encoding.Encoding.GetBytes( value, 0, value.Length, array, offset );
 55378            },
 1379            null,
 1380            null
 1381            );
 382
 383         // Boolean
 1384         yield return CreateSingleBodyInfoWithType(
 1385            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1386            {
 1387               Byte b;
 1388               return count > 0
 1389                  && (
 1390                     ( b = args.Encoding.ReadASCIIByte( array, ref offset ) ) == 'T'
 1391                     || b == 't'
 1392                     );
 1393            },
 1394            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1395            {
 1396               return count > 0 && array[offset] != 0;
 1397            },
 1398            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Boolean value, Boolean isArrayElement ) =>
 1399            {
 1400               return encoding.BytesPerASCIICharacter;
 1401            },
 1402            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Boolean value, Boolean isArrayElement ) =>
 1403            {
 1404               return 1;
 1405            },
 1406            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Boolean value, TSyncTe
 1407            {
 1408               args.Encoding.WriteASCIIByte( array, ref offset, (Byte) ( value ? 't' : 'f' ) );
 1409            },
 1410            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Boolean value, Int32 s
 1411            {
 1412               array[offset] = (Byte) ( value ? 1 : 0 );
 1413            },
 1414            null,
 1415            null
 1416            );
 417
 418         // Int16
 1419         yield return CreateSingleBodyInfoWithType(
 1420            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1421            {
 1422               return (Int16) args.Encoding.ParseInt32Textual( array, ref offset, (count / args.Encoding.BytesPerASCIICh
 1423            },
 1424            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1425            {
 1426               return array.ReadInt16BEFromBytes( ref offset );
 1427            },
 1428            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int16 value, Boolean isArrayElement ) =>
 1429            {
 1430               return encoding.GetTextualIntegerRepresentationSize( value );
 1431            },
 1432            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int16 value, Boolean isArrayElement ) =>
 1433            {
 1434               return sizeof( Int16 );
 1435            },
 1436            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int16 value, TSyncText
 1437            {
 1438               args.Encoding.WriteIntegerTextual( array, ref offset, value );
 1439            },
 1440            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int16 value, Int32 siz
 1441            {
 1442               array.WriteInt16BEToBytes( ref offset, value );
 1443            },
 1444            null,
 1445            null
 1446            );
 447
 448         // Int32
 1449         yield return CreateSingleBodyInfoWithType(
 1450            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1451            {
 69452               return args.Encoding.ParseInt32Textual( array, ref offset, (count / args.Encoding.BytesPerASCIICharacter,
 1453            },
 1454            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1455            {
 1456               return array.ReadInt32BEFromBytes( ref offset );
 1457            },
 1458            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int32 value, Boolean isArrayElement ) =>
 1459            {
 6460               return encoding.GetTextualIntegerRepresentationSize( value );
 1461            },
 1462            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int32 value, Boolean isArrayElement ) =>
 1463            {
 18464               return sizeof( Int32 );
 1465            },
 1466            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 value, TSyncText
 1467            {
 6468               args.Encoding.WriteIntegerTextual( array, ref offset, value );
 6469            },
 1470            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 value, Int32 siz
 1471            {
 19472               array.WriteInt32BEToBytes( ref offset, value );
 19473            },
 1474            null,
 1475            null
 1476            );
 477
 478         // Int64
 1479         yield return CreateSingleBodyInfoWithType(
 1480            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1481            {
 1482               return args.Encoding.ParseInt64Textual( array, ref offset, (count / args.Encoding.BytesPerASCIICharacter,
 1483            },
 1484            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1485            {
 1486               return array.ReadInt64BEFromBytes( ref offset );
 1487            },
 1488            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int64 value, Boolean isArrayElement ) =>
 1489            {
 1490               return encoding.GetTextualIntegerRepresentationSize( value );
 1491            },
 1492            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int64 value, Boolean isArrayElement ) =>
 1493            {
 1494               return sizeof( Int64 );
 1495            },
 1496            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int64 value, TSyncText
 1497            {
 1498               args.Encoding.WriteIntegerTextual( array, ref offset, value );
 1499            },
 1500            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int64 value, Int32 siz
 1501            {
 1502               array.WriteInt64BEToBytes( ref offset, value );
 1503            },
 1504            null,
 1505            null
 1506            );
 507
 508         // Unfortunately, parsing real numbers (single, double, decimal) directly from byte array containing the string
 509         // so right now we have to allocate a new string and parse it.
 510         // It's a bit of a performance killer, so maybe should be fixed one day.
 511         // Single
 1512         yield return CreateSingleBodyInfoWithType(
 1513            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1514            {
 1515               return Single.Parse( args.GetStringWithPool( array, offset, count ), CommonPgSQLTypeFunctionalityInfo.Num
 1516            },
 1517            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1518            {
 1519               return array.ReadSingleBEFromBytes( ref offset );
 1520            },
 1521            null,
 1522            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Single value, Boolean isArrayElement ) =>
 1523            {
 1524               return sizeof( Single );
 1525            },
 1526            null,
 1527            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Single value, Int32 si
 1528            {
 1529               array.WriteSingleBEToBytes( ref offset, value );
 1530            },
 1531            null,
 1532            null
 1533            );
 534
 535         // Double
 1536         yield return CreateSingleBodyInfoWithType(
 1537            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1538            {
 1539               return Double.Parse( args.GetStringWithPool( array, offset, count ), CommonPgSQLTypeFunctionalityInfo.Num
 1540            },
 1541            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1542            {
 1543               return array.ReadDoubleBEFromBytes( ref offset );
 1544            },
 1545            null,
 1546            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Double value, Boolean isArrayElement ) =>
 1547            {
 1548               return sizeof( Double );
 1549            },
 1550            null,
 1551            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Double value, Int32 si
 1552            {
 1553               array.WriteDoubleBEToBytes( ref offset, value );
 1554            },
 1555            null,
 1556            null
 1557            );
 558
 559         // Decimal
 1560         yield return CreateSingleBodyInfoWithType(
 1561            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1562            {
 1563               return Decimal.Parse( args.GetStringWithPool( array, offset, count ), CommonPgSQLTypeFunctionalityInfo.Nu
 1564            },
 1565            null,
 1566            null,
 1567            null,
 1568            null,
 1569            null,
 1570            null,
 1571            null
 1572            );
 573
 574         // Guid
 1575         yield return CreateSingleBodyInfoWithType(
 1576            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1577            {
 1578               return Guid.Parse( args.GetStringWithPool( array, offset, count ) );
 1579            },
 1580            null,
 1581            null,
 1582            null,
 1583            null,
 1584            null,
 1585            null,
 1586            null
 1587            );
 588
 589         // Xml
 1590         yield return CreateSingleBodyInfoWithType(
 1591            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1592            {
 1593               // If there would be "LoadAsync" in XEelement, we could use new PgSQLTypeUnboundInfo constructor directly
 1594               using ( var mStream = new System.IO.MemoryStream( array, offset, count, false ) )
 1595               {
 1596                  return System.Xml.Linq.XElement.Load( mStream );
 1597               }
 1598            },
 1599            null,
 1600            null,
 1601            null,
 1602            null,
 1603            null,
 1604            null,
 1605            null
 1606            );
 607
 608         // PgSQLInterval
 1609         yield return CreateSingleBodyInfoWithType(
 1610            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1611            {
 1612               // TODO interval parsing without string allocation (PgSQLInterval.Load(Stream stream))
 1613               // Or PgSQLInterval.ParseBinaryText
 1614               return PgSQLInterval.Parse( args.GetStringWithPool( array, offset, count ) );
 1615            },
 1616            null,
 1617            // TODO interval string writing without string allocation
 1618            null,
 1619            null,
 1620            null,
 1621            null,
 1622            ( PgSQLTypeDatabaseData dbData, PgSQLInterval pgSQLObject, Type targetType ) =>
 1623            {
 1624               return (TimeSpan) pgSQLObject;
 1625            },
 1626            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 1627            {
 1628               return (TimeSpan) systemObject;
 1629            }
 1630            );
 631
 632         // PgSQLDate
 1633         yield return CreateSingleBodyInfoWithType(
 1634            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1635            {
 1636               return PgSQLDate.ParseBinaryText( args.Encoding, array, ref offset, count );
 1637            },
 1638            null,
 1639            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLDate value, Boolean isArrayElement ) =>
 1640            {
 1641               var retVal = value.GetTextByteCount( encoding );
 1642               if ( isArrayElement && value.NeedsQuotingInArrayElement() )
 1643               {
 1644                  // The " BC" substring contains whitespace, so we must put quotes around this
 1645                  retVal += 2 * encoding.BytesPerASCIICharacter;
 1646               }
 1647               return retVal;
 1648            },
 1649            null,
 1650            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLDate value, TSync
 1651            {
 1652               value.WriteBytesAndPossiblyQuote( args.Encoding, array, ref offset, isArrayElement && value.NeedsQuotingI
 1653            },
 1654            null,
 1655            ( PgSQLTypeDatabaseData dbData, PgSQLDate pgSQLObject, Type targetType ) =>
 1656            {
 1657               return (DateTime) pgSQLObject;
 1658            },
 1659            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 1660            {
 1661               return (PgSQLDate) (DateTime) systemObject;
 1662            }
 1663            );
 664
 665         // PgSQLTime
 1666         yield return CreateSingleBodyInfoWithType(
 1667            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1668            {
 1669               return PgSQLTime.ParseBinaryText( args.Encoding, array, ref offset, count );
 1670            },
 1671            null,
 1672            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTime value, Boolean isArrayElement ) =>
 1673            {
 1674               return value.GetTextByteCount( encoding );
 1675            },
 1676            null,
 1677            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTime value, TSync
 1678            {
 1679               value.WriteTextBytes( args.Encoding, array, ref offset );
 1680            },
 1681            null,
 1682            ( PgSQLTypeDatabaseData dbData, PgSQLTime pgSQLObject, Type targetType ) =>
 1683            {
 1684               if ( typeof( DateTime ).Equals( targetType ) )
 1685               {
 1686                  return (DateTime) pgSQLObject;
 1687               }
 1688               else if ( typeof( TimeSpan ).Equals( targetType ) )
 1689               {
 1690                  return (TimeSpan) pgSQLObject;
 1691               }
 1692               else if ( typeof( PgSQLInterval ).Equals( targetType ) )
 1693               {
 1694                  return (PgSQLInterval) pgSQLObject;
 1695               }
 1696               else
 1697               {
 1698                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 1699               }
 1700            },
 1701            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 1702            {
 1703               var tt = systemObject.GetType();
 1704               if ( typeof( DateTime ).Equals( tt ) )
 1705               {
 1706                  return (PgSQLTime) (DateTime) systemObject;
 1707               }
 1708               else if ( typeof( TimeSpan ).Equals( tt ) )
 1709               {
 1710                  return (PgSQLTime) (TimeSpan) systemObject;
 1711               }
 1712               else if ( typeof( PgSQLInterval ).Equals( tt ) )
 1713               {
 1714                  return (PgSQLTime) (PgSQLInterval) systemObject;
 1715               }
 1716               else
 1717               {
 1718                  throw new InvalidCastException( "Can't cast object " + systemObject + " to time." );
 1719               }
 1720            }
 1721            );
 722
 723         // PgSQLTimeTZ
 1724         yield return CreateSingleBodyInfoWithType(
 1725            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1726            {
 1727               return PgSQLTimeTZ.ParseBinaryText( args.Encoding, array, ref offset, count );
 1728            },
 1729            null,
 1730            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTimeTZ value, Boolean isArrayElement ) =>
 1731            {
 1732               return value.GetTextByteCount( encoding );
 1733            },
 1734            null,
 1735            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTimeTZ value, TSy
 1736            {
 1737               value.WriteTextBytes( args.Encoding, array, ref offset );
 1738            },
 1739            null,
 1740            ( PgSQLTypeDatabaseData dbData, PgSQLTimeTZ pgSQLObject, Type targetType ) =>
 1741            {
 1742               if ( typeof( DateTime ).Equals( targetType ) )
 1743               {
 1744                  return (DateTime) pgSQLObject;
 1745               }
 1746               else if ( typeof( TimeSpan ).Equals( targetType ) )
 1747               {
 1748                  return (TimeSpan) pgSQLObject;
 1749               }
 1750               else
 1751               {
 1752                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 1753               }
 1754            },
 1755            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 1756            {
 1757               var tt = systemObject.GetType();
 1758               if ( typeof( DateTime ).Equals( tt ) )
 1759               {
 1760                  return (PgSQLTimeTZ) (DateTime) systemObject;
 1761               }
 1762               else if ( typeof( TimeSpan ).Equals( tt ) )
 1763               {
 1764                  return (PgSQLTimeTZ) (TimeSpan) systemObject;
 1765               }
 1766               else
 1767               {
 1768                  throw new InvalidCastException( "Can't cast object " + systemObject + " to time with time zone." );
 1769               }
 1770            }
 1771            );
 772
 773         // PgSQLTimestamp
 1774         yield return CreateSingleBodyInfoWithType(
 1775            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1776            {
 2777               return PgSQLTimestamp.ParseBinaryText( args.Encoding, array, ref offset, count );
 1778            },
 1779            null,
 1780            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTimestamp value, Boolean isArrayElement ) =>
 1781            {
 1782               var retVal = value.GetTextByteCount( encoding );
 1783               if ( isArrayElement && value.NeedsQuotingInArrayElement() )
 1784               {
 1785                  // There will be a space -> the value must be quoted
 1786                  retVal += 2 * encoding.BytesPerASCIICharacter;
 1787               }
 1788               return retVal;
 1789            },
 1790            null,
 1791            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTimestamp value, 
 1792            {
 1793               value.WriteBytesAndPossiblyQuote( args.Encoding, array, ref offset, isArrayElement && value.NeedsQuotingI
 1794            },
 1795            null,
 1796            ( PgSQLTypeDatabaseData dbData, PgSQLTimestamp pgSQLObject, Type targetType ) =>
 1797            {
 2798               if ( typeof( DateTime ).Equals( targetType ) )
 1799               {
 2800                  return (DateTime) pgSQLObject;
 1801               }
 1802               else
 1803               {
 1804                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 1805               }
 1806            },
 1807            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 1808            {
 1809               var tt = systemObject.GetType();
 1810               if ( typeof( DateTime ).Equals( tt ) )
 1811               {
 1812                  return (PgSQLTimestamp) (DateTime) systemObject;
 1813               }
 1814               else
 1815               {
 1816                  throw new InvalidCastException( "Can't cast object " + systemObject + " to timestamp." );
 1817               }
 1818            }
 1819            );
 820
 821         // PgSQLTimestampTZ
 1822         yield return CreateSingleBodyInfoWithType(
 1823            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1824            {
 1825               return PgSQLTimestampTZ.ParseBinaryText( args.Encoding, array, ref offset, count );
 1826            },
 1827            null,
 1828            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTimestampTZ value, Boolean isArrayElement ) 
 1829            {
 1830               var retVal = value.GetTextByteCount( encoding );
 1831               if ( isArrayElement && value.NeedsQuotingInArrayElement() )
 1832               {
 1833                  retVal += 2 * encoding.BytesPerASCIICharacter;
 1834               }
 1835               return retVal;
 1836            },
 1837            null,
 1838            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTimestampTZ value
 1839            {
 1840               value.WriteBytesAndPossiblyQuote( args.Encoding, array, ref offset, isArrayElement && value.NeedsQuotingI
 1841            },
 1842            null,
 1843            ( PgSQLTypeDatabaseData dbData, PgSQLTimestampTZ pgSQLObject, Type targetType ) =>
 1844            {
 1845               if ( typeof( DateTime ).Equals( targetType ) )
 1846               {
 1847                  return (DateTime) pgSQLObject;
 1848               }
 1849               else if ( typeof( DateTimeOffset ).Equals( targetType ) )
 1850               {
 1851                  return (DateTimeOffset) pgSQLObject;
 1852               }
 1853               else
 1854               {
 1855                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 1856               }
 1857            },
 1858            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 1859            {
 1860               var tt = systemObject.GetType();
 1861               if ( typeof( DateTime ).Equals( tt ) )
 1862               {
 1863                  return (PgSQLTimestampTZ) (DateTime) systemObject;
 1864               }
 1865               else if ( typeof( DateTimeOffset ).Equals( tt ) )
 1866               {
 1867                  return (PgSQLTimestampTZ) (DateTimeOffset) systemObject;
 1868               }
 1869               else
 1870               {
 1871                  throw new InvalidCastException( "Can't cast object " + systemObject + " to timestamp with time zone." 
 1872               }
 1873            }
 1874            );
 875
 876         // Byte[]
 1877         yield return CreateSingleBodyInfoWithType(
 1878            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1879            {
 1880               // Byte[] textual format is "\x<hex decimal pairs>"
 1881               var encoding = args.Encoding;
 1882               Byte[] retVal;
 1883               if ( count > 2
 1884               && encoding.ReadASCIIByte( array, ref offset ) == '\\'
 1885               && encoding.ReadASCIIByte( array, ref offset ) == 'x'
 1886               )
 1887               {
 1888                  var len = ( count - offset ) / encoding.BytesPerASCIICharacter / 2;
 1889                  retVal = new Byte[len];
 1890                  for ( var i = 0; i < len; ++i )
 1891                  {
 1892                     retVal[i] = encoding.ReadHexDecimal( array, ref offset );
 1893                  }
 1894               }
 1895               else
 1896               {
 1897                  throw new PgSQLException( "Bytea strings must start with \"\\x\"." );
 1898               }
 1899
 1900               return retVal;
 1901            },
 1902            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 1903            {
 1904               // Binary format is just raw byte array
 1905               var retVal = new Byte[count];
 1906               Array.Copy( array, offset, retVal, 0, count );
 1907               return retVal;
 1908            },
 1909            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Byte[] value, Boolean isArrayElement ) =>
 1910            {
 1911               // Text size is 2 ASCII bytes + 2 ASCII bytes per each actual byte
 1912               var retVal = ( 2 + 2 * value.Length ) * encoding.BytesPerASCIICharacter;
 1913               if ( isArrayElement )
 1914               {
 1915                  // Always need quotation since there is a '\\' character
 1916                  retVal += 2 * encoding.BytesPerASCIICharacter;
 1917               }
 1918               return retVal;
 1919            },
 1920            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Byte[] value, Boolean isArrayElement ) =>
 1921            {
 1922               // Binary size is same as array size
 2923               return value.Length;
 1924            },
 1925            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Byte[] value, TSyncTex
 1926            {
 1927               // Write all text to array
 1928               var encoding = args.Encoding;
 1929               if ( isArrayElement )
 1930               {
 1931                  encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 1932               }
 1933
 1934               encoding
 1935                  .WriteASCIIByte( array, ref offset, (Byte) '\\' )
 1936                                 .WriteASCIIByte( array, ref offset, (Byte) 'x' );
 1937               foreach ( var b in value )
 1938               {
 1939                  encoding.WriteHexDecimal( array, ref offset, b );
 1940               }
 1941               if ( isArrayElement )
 1942               {
 1943                  encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 1944               }
 1945            },
 1946            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Byte[] value, Int32 ad
 1947            {
 1948               // Just copy array
 2949               Array.Copy( value, 0, array, offset, value.Length );
 2950            },
 1951            null,
 1952            null
 1953            );
 1954      }
 955
 956      private static (Type Type, DefaultPgSQLTypeFunctionality<TValue> Functionality) CreateSingleBodyInfoWithType<TValu
 957         ReadFromBackendSync<TValue> text2CLR,
 958         ReadFromBackendSync<TValue> binary2CLR,
 959         CalculateBackendSize<TValue, EitherOr<Int32, String>> textSize,
 960         CalculateBackendSize<TValue, Int32> clr2BinarySize,
 961         WriteToBackendSync<TValue, TSyncTextualSizeInfo> clr2Text,
 962         WriteToBackendSync<TValue, Int32> clr2Binary,
 963         ChangePgSQLToSystem<TValue> pgSQL2System,
 964         ChangeSystemToPgSQL<TValue> system2PgSQL
 965         )
 966      {
 17967         return (typeof( TValue ), DefaultPgSQLTypeFunctionality<TValue>.CreateSingleBodyUnboundInfo( text2CLR, binary2C
 968      }
 969   }
 970
 971}
 972
 973public static partial class E_CBAM
 974{
 975
 976   internal static Boolean NeedsQuotingInArrayElement( this PgSQLDate date )
 977   {
 978      // Values with year less than 0 will be prefixed with " BC" suffix, thus requiring quoting
 979      return date.Year < 0;
 980   }
 981
 982   internal static Boolean NeedsQuotingInArrayElement( this PgSQLTimestamp timestamp )
 983   {
 984      // Anything other than "Infinity" and "-Infinity" will need quoting since there will be a space
 985      return timestamp.IsFinite;
 986   }
 987
 988   internal static Boolean NeedsQuotingInArrayElement( this PgSQLTimestampTZ timestamp )
 989   {
 990      // Anything other than "Infinity" and "-Infinity" will need quoting since there will be a space
 991      return timestamp.IsFinite;
 992   }
 993
 994   internal delegate void WriteTextBytes<T>( T value, IEncodingInfo encoding, Byte[] array, ref Int32 idx );
 995
 996   internal static void WriteBytesAndPossiblyQuote<T>(
 997      this T value,
 998      IEncodingInfo encoding,
 999      Byte[] array,
 1000      ref Int32 index,
 1001      Boolean needsQuoting,
 1002      WriteTextBytes<T> writeTextBytes
 1003      )
 1004   {
 1005      if ( needsQuoting )
 1006      {
 1007         encoding.WriteASCIIByte( array, ref index, (Byte) '"' );
 1008      }
 1009      writeTextBytes( value, encoding, array, ref index );
 1010      if ( needsQuoting )
 1011      {
 1012         encoding.WriteASCIIByte( array, ref index, (Byte) '"' );
 1013      }
 1014   }
 1015}

Methods/Properties

.ctor(CBAM.SQL.PostgreSQL.PgSQLConnectionVendorFunctionality,System.Boolean,System.Boolean,CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,UtilPack.ResizableArray`1<System.Byte>,System.Collections.Generic.IDictionary`2<System.String,System.String>,CBAM.SQL.PostgreSQL.TransactionStatus,System.Int32,System.Net.Sockets.Socket)
TypeRegistry()
BackendProcessID()
ServerParameters()
CreateReservationObject(CBAM.SQL.SQLStatementBuilderInformation)
ValidateStatementOrThrow(CBAM.SQL.SQLStatementBuilderInformation)
GetVariablesForExtendedQuerySequence(CBAM.SQL.SQLStatementBuilderInformation,CBAM.SQL.PostgreSQL.TypeRegistry,System.Func`3<CBAM.SQL.SQLStatementBuilderInformation,System.Int32,CBAM.SQL.StatementParameter>)
GetIOArgs(UtilPack.ResizableArray`1<System.Byte>,System.Nullable`1<System.Threading.CancellationToken>)
ExecuteStatementAsBatch()
SendMessagesForBatch()
ReceiveMessagesForBatch()
ExecuteStatementAsPrepared()
>c__DisplayClass20_1/<<ExecuteStatementAsPrepared()
ExecuteStatementAsSimple()
>c__DisplayClass21_0/<<ExecuteStatementAsSimple()
MoveNextAsync()
>c__DisplayClass22_0/<<MoveNextAsync()
LastSeenTransactionStatus()
LastSeenTransactionStatus(CBAM.SQL.PostgreSQL.TransactionStatus)
PerformDisposeStatementAsync()
MessageIOArgs()
Buffer()
Stream()
Socket()
DataRowColumnSizes()
DisableBinaryProtocolSend()
DisableBinaryProtocolReceive()
EnqueuedNotifications()
ConvertFromBytes()
ReadMessagesUntilMeaningful()
PerformClose()
SocketHasDataPending()
CheckNotificationsAsync()
>c__DisplayClass55_1/<<CheckNotificationsAsync()
ListenToNotificationsAsync()
>c__DisplayClass56_0/<<ListenToNotificationsAsync()
PerformStartup()
DoConnectionInitialization()
ProcessAuth()
GetPasswordBytes(CBAM.SQL.PostgreSQL.PgSQLConnectionCreationInfo,System.ValueTuple`4<CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,System.Threading.CancellationToken,UtilPack.ResizableArray`1<System.Byte>>)
HandleMD5Authentication(System.ValueTuple`4<CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,System.Threading.CancellationToken,UtilPack.ResizableArray`1<System.Byte>>,CBAM.SQL.PostgreSQL.Implementation.AuthenticationResponse,System.String,System.Byte[])
HandleSASLAuthentication_Start(CBAM.SQL.PostgreSQL.PgSQLConnectionCreationInfo,System.ValueTuple`4<CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,System.Threading.CancellationToken,UtilPack.ResizableArray`1<System.Byte>>,System.String,CBAM.SQL.PostgreSQL.Implementation.AuthenticationResponse)
HandleSASLAuthentication_Continue(System.ValueTuple`4<CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,System.Threading.CancellationToken,UtilPack.ResizableArray`1<System.Byte>>,CBAM.SQL.PostgreSQL.Implementation.AuthenticationResponse,System.Object)
HandleSASLAuthentication_Final(CBAM.SQL.PostgreSQL.PgSQLConnectionCreationInfo,System.ValueTuple`4<CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,System.Threading.CancellationToken,UtilPack.ResizableArray`1<System.Byte>>,CBAM.SQL.PostgreSQL.Implementation.AuthenticationResponse,System.Object)
HandleSASLAuthentication_ContinueOrFinal(System.ValueTuple`4<CBAM.SQL.PostgreSQL.BackendABIHelper,System.IO.Stream,System.Threading.CancellationToken,UtilPack.ResizableArray`1<System.Byte>>,CBAM.SQL.PostgreSQL.Implementation.AuthenticationResponse,System.Object)
DisposeSASLState(System.Object)
.ctor(System.Boolean,System.String)
IsSimple()
StatementName()
RFQEncountered()
RFQSeen()
.cctor()
ReadTypesFromServer()
CreateDefaultTypesInfos()
CreateSingleBodyInfoWithType(CBAM.SQL.PostgreSQL.ReadFromBackendSync`1<TValue>,CBAM.SQL.PostgreSQL.ReadFromBackendSync`1<TValue>,CBAM.SQL.PostgreSQL.CalculateBackendSize`2<TValue,UtilPack.EitherOr`2<System.Int32,System.String>>,CBAM.SQL.PostgreSQL.CalculateBackendSize`2<TValue,System.Int32>,CBAM.SQL.PostgreSQL.WriteToBackendSync`2<TValue,System.ValueTuple`2<System.Int32,System.String>>,CBAM.SQL.PostgreSQL.WriteToBackendSync`2<TValue,System.Int32>,CBAM.SQL.PostgreSQL.ChangePgSQLToSystem`1<TValue>,CBAM.SQL.PostgreSQL.ChangeSystemToPgSQL`1<TValue>)