Summary

Class:E_CBAM
Assembly:CBAM.SQL.PostgreSQL.Implementation
File(s):/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/ConnectionPoolConfiguration.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Reading.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Types.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Writing.cs
/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/VendorSpecific.cs
Covered lines:51
Uncovered lines:10
Coverable lines:61
Total lines:3233
Line coverage:83.6%
Branch coverage:46.5%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
CreateCopy(...)3401%0.5%
ReadPgInt16(...)101%0%
ReadPgInt16Count(...)101%0%
NeedsQuotingInArrayElement(...)100%0%
NeedsQuotingInArrayElement(...)100%0%
NeedsQuotingInArrayElement(...)100%0%
WriteBytesAndPossiblyQuote(...)400%0%
GetStringSize(...)201%1%
WriteString()101%1%
IsOfValue(...)200%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/ConnectionPoolConfiguration.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.IO;
 22using System.Net;
 23using System.Text;
 24using System.Threading.Tasks;
 25using CBAM.SQL.PostgreSQL.Implementation;
 26using UtilPack;
 27using CBAM.Abstractions.Implementation;
 28using CBAM.SQL.PostgreSQL;
 29using IOUtils.Network.Configuration;
 30using FluentCryptography.SASL;
 31using FluentCryptography.SASL.SCRAM;
 32
 33#if !NETSTANDARD1_0
 34using IOUtils.Network.ResourcePooling;
 35#endif
 36
 37namespace CBAM.SQL.PostgreSQL
 38{
 39
 40   /// <summary>
 41   /// This class represents the passive data related to creation of new <see cref="PgSQLConnection"/>.
 42   /// Typically, this class holds the values written in some configuration file on a disk.
 43   /// Use <c>Microsoft.Extensions.Configuration.Binder</c> NuGet package to automatize the creation of this class and p
 44   /// </summary>
 45   /// <remarks>
 46   /// See <see href="https://github.com/CometaSolutions/CBAM/tree/develop/Source/CBAM.SQL.PostgreSQL.Implementation"/> 
 47   /// </remarks>
 48   /// <seealso cref="PgSQLConnectionCreationInfo"/>
 49   public class PgSQLConnectionCreationInfoData : NetworkConnectionCreationInfoData<PgSQLConnectionConfiguration, PgSQLI
 50   {
 51
 52
 53   }
 54
 55   /// <summary>
 56   /// This class contains all passive configuration data related to opening a socket connection when initializing new <
 57   /// </summary>
 58   public class PgSQLConnectionConfiguration : NetworkConnectionConfiguration
 59   {
 60   }
 61
 62   /// <summary>
 63   /// This class contains all passive configuration data related to initialization routine of new <see cref="PgSQLConne
 64   /// </summary>
 65   public class PgSQLInitializationConfiguration : NetworkInitializationConfiguration<PgSQLProtocolConfiguration, PgSQLP
 66   {
 67      /// <summary>
 68      /// Gets or sets the type containing passive configuration data about the database to connect to.
 69      /// </summary>
 70      /// <value>The type containing passive configuration data about the database to connect to.</value>
 71      /// <seealso cref="PgSQLDatabaseConfiguration"/>
 72      public PgSQLDatabaseConfiguration Database { get; set; }
 73
 74   }
 75
 76   /// <summary>
 77   /// This class contains all passive configuration data related to behaviour of PgSQL connections when they are used w
 78   /// </summary>
 79   public class PgSQLPoolingConfiguration : NetworkPoolingConfiguration
 80   {
 81   }
 82
 83   /// <summary>
 84   /// This class contains all passive configuration data related to authentication of new <see cref="PgSQLConnection"/>
 85   /// </summary>
 86   public class PgSQLAuthenticationConfiguration
 87   {
 88
 89      internal static readonly Encoding PasswordByteEncoding = new UTF8Encoding( false, true );
 90
 91      /// <summary>
 92      /// Gets or sets the username to use when connecting to the database.
 93      /// </summary>
 94      /// <value>The username to use when connecting to the database.</value>
 95      public String Username { get; set; }
 96
 97      /// <summary>
 98      /// Gets the textual password as byte array, to use along with <see cref="Username"/> when connecting to the datab
 99      /// </summary>
 100      /// <value>The textual password as byte array, to use along with <see cref="Username"/> when connecting to the dat
 101      /// <seealso cref="PasswordDigest"/>
 102      public Byte[] PasswordBytes { get; private set; }
 103
 104      /// <summary>
 105      /// Gets or sets the digest of the cleartext password.
 106      /// </summary>
 107      /// <value>The digest of the cleartext password.</value>
 108      /// <remarks>
 109      /// This property will *only* be used in SCRAM authentication, if server chooses to perform it.
 110      /// The SCRAM authentication allows to use the digest of the password instead of cleartext password.
 111      /// </remarks>
 112      public Byte[] PasswordDigest { get; set; }
 113
 114      /// <summary>
 115      /// Gets the password, as <see cref="String"/>, to use along with <see cref="Username"/> when connecting to the da
 116      /// </summary>
 117      /// <value>The password, as <see cref="String"/>, to use along with <see cref="Username"/> when connecting to the 
 118      public String Password
 119      {
 120         get
 121         {
 122            var arr = this.PasswordBytes;
 123            return arr == null ? null : PasswordByteEncoding.GetString( arr, 0, arr.Length );
 124         }
 125         set
 126         {
 127            this.PasswordBytes = value == null ? null : PasswordByteEncoding.GetBytes( value );
 128         }
 129      }
 130   }
 131
 132   /// <summary>
 133   /// This class contains all passive configuration data related to selecting which database the <see cref="PgSQLConnec
 134   /// </summary>
 135   public class PgSQLDatabaseConfiguration
 136   {
 137
 138      /// <summary>
 139      /// Gets or sets the name of the database that the <see cref="PgSQLConnection"/> should be connected to.
 140      /// </summary>
 141      /// <value>The name of the database that the <see cref="PgSQLConnection"/> should be connected to.</value>
 142      public String Name { get; set; }
 143
 144      /// <summary>
 145      /// Gets the search path (<see href="https://www.postgresql.org/docs/current/static/runtime-config-client.html"/>)
 146      /// </summary>
 147      /// <value>The search path (<see href="https://www.postgresql.org/docs/current/static/runtime-config-client.html"/
 148      public String SearchPath { get; set; }
 149   }
 150
 151   /// <summary>
 152   /// This class contains all passive data configuration related to protocol used to communicate with backend process.
 153   /// </summary>
 154   public class PgSQLProtocolConfiguration
 155   {
 156      /// <summary>
 157      /// Gets or sets the value indicating whether SQL type IDs should be re-read from database, even though they have 
 158      /// </summary>
 159      /// <value>The value indicating whether SQL type IDs should be re-read from database</value>
 160      public Boolean ForceTypeIDLoad { get; set; }
 161
 162      /// <summary>
 163      /// Gets or sets the value indicating whether <see cref="DataFormat.Binary"/> should be disabled when sending data
 164      /// </summary>
 165      /// <value>The value indicating whether <see cref="DataFormat.Binary"/> should be disabled when sending data to th
 166      public Boolean DisableBinaryProtocolSend { get; set; }
 167
 168      /// <summary>
 169      /// Gets or sets the value indicating whether <see cref="DataFormat.Binary"/> should be disabled when receiving da
 170      /// </summary>
 171      /// <value>The value indicating whether <see cref="DataFormat.Binary"/> should be disabled when receiving data fro
 172      public Boolean DisableBinaryProtocolReceive { get; set; }
 173   }
 174
 175   /// <summary>
 176   /// This class binds together the passive configuration data of the <see cref="PgSQLConnectionCreationInfoData"/> and
 177   /// </summary>
 178   public sealed class PgSQLConnectionCreationInfo : NetworkConnectionCreationInfo<PgSQLConnectionCreationInfoData, PgSQ
 179   {
 180      private const String SCRAM_SHA_256 = "SCRAM-SHA-256";
 181      private const String SCRAM_SHA_512 = "SCRAM-SHA-512";
 182
 183      /// <summary>
 184      /// Creates a new instance of <see cref="PgSQLConnectionCreationInfo"/> with given <see cref="PgSQLConnectionCreat
 185      /// </summary>
 186      /// <param name="data">The <see cref="PgSQLConnectionCreationInfoData"/> to use.</param>
 187      /// <exception cref="ArgumentNullException">If <paramref name="data"/> is <c>null</c>.</exception>
 188      /// <remarks>
 189      /// In .NET Core App 1.1+ and .NET Desktop 4.0+ environments this will also set up default values for <see cref="P
 190      /// </remarks>
 191      public PgSQLConnectionCreationInfo(
 192         PgSQLConnectionCreationInfoData data
 193         ) : base( data )
 194      {
 195         this.CreateSASLMechanism = ( names ) =>
 196         {
 197            FluentCryptography.Digest.BlockDigestAlgorithm algorithm;
 198            String mechanismName;
 199
 200            if ( String.IsNullOrEmpty( names ) )
 201            {
 202               algorithm = null;
 203               mechanismName = null;
 204            }
 205            else
 206            {
 207               if ( names.IndexOf( SCRAM_SHA_512 ) >= 0 )
 208               {
 209                  algorithm = new FluentCryptography.Digest.SHA512();
 210                  mechanismName = SCRAM_SHA_512;
 211               }
 212               else if ( names.IndexOf( SCRAM_SHA_256 ) >= 0 )
 213               {
 214                  algorithm = new FluentCryptography.Digest.SHA256();
 215                  mechanismName = SCRAM_SHA_256;
 216               }
 217               else
 218               {
 219                  algorithm = null;
 220                  mechanismName = null;
 221               }
 222            }
 223
 224            return (algorithm?.CreateSASLClientSCRAM(), mechanismName);
 225         };
 226      }
 227      /// <summary>
 228      /// This callback will be used during SCRAM authentication to select the <see cref="SASLMechanism"/> based on the 
 229      /// The constructor will set this to default value which supports SCRAM-SHA-256 and SCRAM-SHA-512, but this may be
 230      /// </summary>
 231      /// <value>The callback to select <see cref="SASLMechanism"/> from a list of SASL mechanisms advertised by backend
 232      /// <remarks>
 233      /// This callback should return a tuple of <see cref="SASLMechanism"/> and the name of it.
 234      /// </remarks>
 235      public Func<String, (SASLMechanism, String)> CreateSASLMechanism { get; set; }
 236
 237      /// <summary>
 238      /// This callback will be used during SCRAM authentication, when the user is successfully authenticated.
 239      /// It will receive a digest of the cleartext password, so that it can be e.g. saved for later purpose.
 240      /// </summary>
 241      /// <value>The callback to call on successful SASL SCRAM authentication, receiving password digest as argument.</v
 242      public Action<Byte[]> OnSASLSCRAMSuccess { get; set; }
 243
 244   }
 245}
 246
 247/// <summary>
 248/// This class contains extension methods for types defined in this assembly.
 249/// </summary>
 250public static partial class E_CBAM
 251{
 252   /// <summary>
 253   /// This is helper method to create a new deep copy of this <see cref="PgSQLConnectionCreationInfoData"/> instance.
 254   /// </summary>
 255   /// <param name="data">This <see cref="PgSQLConnectionCreationInfoData"/>.</param>
 256   /// <returns>A new instance of <see cref="PgSQLConnectionCreationInfoData"/>, with all values deeply copied from this
 257   /// <exception cref="NullReferenceException">If this <see cref="PgSQLConnectionCreationInfoData"/> is <c>null</c>.</e
 258   public static PgSQLConnectionCreationInfoData CreateCopy( this PgSQLConnectionCreationInfoData data )
 259   {
 260
 261#if !NETSTANDARD1_0
 4262      var conn = data.Connection;
 263#endif
 264
 4265      var init = data.Initialization;
 4266      var db = init?.Database;
 4267      var protocol = init?.Protocol;
 4268      var pool = init?.ConnectionPool;
 4269      var auth = init?.Authentication;
 270
 4271      return new PgSQLConnectionCreationInfoData()
 4272      {
 4273#if !NETSTANDARD1_0
 4274         Connection = new PgSQLConnectionConfiguration()
 4275         {
 4276            Host = conn?.Host,
 4277            Port = conn?.Port ?? 0,
 4278            ConnectionSSLMode = conn?.ConnectionSSLMode ?? ConnectionSSLMode.NotRequired,
 4279            SSLProtocols = conn?.SSLProtocols ?? PgSQLConnectionConfiguration.DEFAULT_SSL_PROTOCOL
 4280         },
 4281#endif
 4282         Initialization = new PgSQLInitializationConfiguration()
 4283         {
 4284            Database = new PgSQLDatabaseConfiguration()
 4285            {
 4286               Name = db?.Name,
 4287               SearchPath = db?.SearchPath
 4288            },
 4289            Authentication = new PgSQLAuthenticationConfiguration()
 4290            {
 4291               Username = auth?.Username,
 4292               Password = auth?.Password,
 4293               PasswordDigest = auth?.PasswordDigest
 4294            },
 4295            Protocol = new PgSQLProtocolConfiguration()
 4296            {
 4297               ForceTypeIDLoad = protocol?.ForceTypeIDLoad ?? false,
 4298               DisableBinaryProtocolSend = protocol?.DisableBinaryProtocolSend ?? false,
 4299               DisableBinaryProtocolReceive = protocol?.DisableBinaryProtocolReceive ?? false
 4300            },
 4301            ConnectionPool = new PgSQLPoolingConfiguration()
 4302            {
 4303               ConnectionsOwnStringPool = pool?.ConnectionsOwnStringPool ?? false
 4304            }
 4305         }
 4306      };
 307   }
 308}

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Reading.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.SQL.PostgreSQL;
 20using CBAM.SQL.PostgreSQL.Implementation;
 21using System;
 22using System.Collections.Generic;
 23using System.IO;
 24using System.Text;
 25using System.Threading;
 26using System.Threading.Tasks;
 27using UtilPack;
 28using MessageIOArgs = System.ValueTuple<CBAM.SQL.PostgreSQL.BackendABIHelper, System.IO.Stream, System.Threading.Cancell
 29
 30namespace CBAM.SQL.PostgreSQL.Implementation
 31{
 32   internal abstract class BackendMessageObject
 33   {
 34
 35      internal BackendMessageObject( BackendMessageCode code )
 36      {
 37         this.Code = code;
 38      }
 39
 40      internal BackendMessageCode Code { get; }
 41
 42      public static async ValueTask<(BackendMessageObject msg, Int32 msgSize)> ReadBackendMessageAsync(
 43         MessageIOArgs ioArgs,
 44         ResizableArray<ResettableTransformable<Int32?, Int32>> columnSizes
 45         )
 46      {
 47         var args = ioArgs.Item1;
 48         var stream = ioArgs.Item2;
 49         var token = ioArgs.Item3;
 50         var buffer = ioArgs.Item4;
 51         await stream.ReadSpecificAmountAsync( buffer.Array, 0, 5, token );
 52         var code = (BackendMessageCode) buffer.Array[0];
 53         var length = buffer.Array.ReadInt32BEFromBytesNoRef( 1 );
 54         var remaining = length - sizeof( Int32 );
 55         if ( code != BackendMessageCode.DataRow && code != BackendMessageCode.CopyData )
 56         {
 57            // Just read the whole message at once for everything else except DataRow and CopyData messages
 58            buffer.CurrentMaxCapacity = remaining;
 59            await stream.ReadSpecificAmountAsync( buffer.Array, 0, remaining, token );
 60            remaining = 0;
 61         }
 62         var array = buffer.Array;
 63         var encoding = args.Encoding.Encoding;
 64
 65         BackendMessageObject result;
 66         switch ( code )
 67         {
 68            case BackendMessageCode.AuthenticationRequest:
 69               result = new AuthenticationResponse( array, length );
 70               break;
 71            case BackendMessageCode.ErrorResponse:
 72               result = new PgSQLErrorObject( array, encoding, true );
 73               break;
 74            case BackendMessageCode.NoticeResponse:
 75               result = new PgSQLErrorObject( array, encoding, false );
 76               break;
 77            case BackendMessageCode.RowDescription:
 78               result = new RowDescription( array, encoding );
 79               break;
 80            case BackendMessageCode.DataRow:
 81               (result, remaining) = await DataRowObject.ReadDataRow( stream, token, array, columnSizes, remaining );
 82               break;
 83            case BackendMessageCode.ParameterDescription:
 84               result = new ParameterDescription( array );
 85               break;
 86            case BackendMessageCode.ParameterStatus:
 87               result = new ParameterStatus( array, encoding );
 88               break;
 89            case BackendMessageCode.ReadyForQuery:
 90               result = new ReadyForQuery( array );
 91               break;
 92            case BackendMessageCode.BackendKeyData:
 93               result = new BackendKeyData( array );
 94               break;
 95            case BackendMessageCode.CommandComplete:
 96               result = new CommandComplete( array, encoding );
 97               break;
 98            case BackendMessageCode.NotificationResponse:
 99               result = new NotificationMessage( array, encoding );
 100               break;
 101            case BackendMessageCode.CopyInResponse:
 102               result = new CopyInOrOutMessage( array, true );
 103               break;
 104            case BackendMessageCode.CopyOutResponse:
 105               result = new CopyInOrOutMessage( array, false );
 106               break;
 107            case BackendMessageCode.CopyData:
 108               result = new CopyDataMessage( length );
 109               break;
 110            case BackendMessageCode.ParseComplete:
 111               result = MessageWithNoContents.PARSE_COMPLETE;
 112               break;
 113            case BackendMessageCode.BindComplete:
 114               result = MessageWithNoContents.BIND_COMPLETE;
 115               break;
 116            case BackendMessageCode.EmptyQueryResponse:
 117               result = MessageWithNoContents.EMPTY_QUERY;
 118               break;
 119            case BackendMessageCode.NoData:
 120               result = MessageWithNoContents.NO_DATA;
 121               break;
 122            case BackendMessageCode.CopyDone:
 123               result = MessageWithNoContents.COPY_DONE;
 124               break;
 125            case BackendMessageCode.CloseComplete:
 126               result = MessageWithNoContents.CLOSE_COMPLETE;
 127               break;
 128            default:
 129               throw new NotSupportedException( "Not supported backend response: " + code );
 130         }
 131
 132         return (result, remaining);
 133      }
 134   }
 135
 136   internal sealed class PgSQLErrorObject : BackendMessageObject
 137   {
 138      private readonly PgSQLError _error;
 139
 140      public PgSQLErrorObject( Byte[] array, Encoding encoding, Boolean isError )
 141         : base( isError ? BackendMessageCode.ErrorResponse : BackendMessageCode.NoticeResponse )
 142      {
 143         String severity = null,
 144            code = null,
 145            message = null,
 146            detail = null,
 147            hint = null,
 148            position = null,
 149            internalPosition = null,
 150            internalQuery = null,
 151            where = null,
 152            file = null,
 153            line = null,
 154            routine = null,
 155            schemaName = null,
 156            tableName = null,
 157            columnName = null,
 158            datatypeName = null,
 159            constraintName = null;
 160
 161         Byte fieldType;
 162         var offset = 0;
 163         do
 164         {
 165            fieldType = array.ReadByteFromBytes( ref offset );
 166            if ( fieldType != 0 )
 167            {
 168               var str = array.ReadZeroTerminatedStringFromBytes( ref offset, encoding );
 169               switch ( fieldType )
 170               {
 171                  case (Byte) 'S':
 172                     severity = str;
 173                     break;
 174                  case (Byte) 'C':
 175                     code = str;
 176                     break;
 177                  case (Byte) 'M':
 178                     message = str;
 179                     break;
 180                  case (Byte) 'D':
 181                     detail = str;
 182                     break;
 183                  case (Byte) 'H':
 184                     hint = str;
 185                     break;
 186                  case (Byte) 'P':
 187                     position = str;
 188                     break;
 189                  case (Byte) 'p':
 190                     internalPosition = str;
 191                     break;
 192                  case (Byte) 'q':
 193                     internalQuery = str;
 194                     break;
 195                  case (Byte) 'W':
 196                     where = str;
 197                     break;
 198                  case (Byte) 'F':
 199                     file = str;
 200                     break;
 201                  case (Byte) 'L':
 202                     line = str;
 203                     break;
 204                  case (Byte) 'R':
 205                     routine = str;
 206                     break;
 207                  case (Byte) 's':
 208                     schemaName = str;
 209                     break;
 210                  case (Byte) 't':
 211                     tableName = str;
 212                     break;
 213                  case (Byte) 'c':
 214                     columnName = str;
 215                     break;
 216                  case (Byte) 'd':
 217                     datatypeName = str;
 218                     break;
 219                  case (Byte) 'n':
 220                     constraintName = str;
 221                     break;
 222                  default:
 223                     // Unknown error field, just continue
 224                     break;
 225               }
 226            }
 227         } while ( fieldType != 0 );
 228
 229         this._error = new PgSQLError(
 230            severity,
 231            code,
 232            message,
 233            detail,
 234            hint,
 235            position,
 236            internalPosition,
 237            internalQuery,
 238            where,
 239            file,
 240            line,
 241            routine,
 242            schemaName,
 243            tableName,
 244            columnName,
 245            datatypeName,
 246            constraintName
 247            );
 248      }
 249
 250      internal PgSQLError Error
 251      {
 252         get
 253         {
 254            return this._error;
 255         }
 256      }
 257   }
 258
 259   internal sealed class AuthenticationResponse : BackendMessageObject
 260   {
 261      internal enum AuthenticationRequestType
 262      {
 263         AuthenticationOk = 0,
 264         AuthenticationKerberosV4 = 1,
 265         AuthenticationKerberosV5 = 2,
 266         AuthenticationClearTextPassword = 3,
 267         AuthenticationCryptPassword = 4,
 268         AuthenticationMD5Password = 5,
 269         AuthenticationSCMCredential = 6,
 270         AuthenticationGSS = 7,
 271         AuthenticationGSSContinue = 8,
 272         AuthenticationSSPI = 9,
 273         AuthenticationSASL = 10,
 274         AuthenticationSASLContinue = 11,
 275         AuthenticationSASLFinal = 12
 276      }
 277
 278      public AuthenticationResponse( Byte[] array, Int32 messageLength )
 279         : base( BackendMessageCode.AuthenticationRequest )
 280      {
 281         var offset = 0;
 282         this.RequestType = (AuthenticationRequestType) array.ReadPgInt32( ref offset );
 283         // Don't count in message length int32 + auth request type int32
 284         this.AdditionalDataInfo = (offset, messageLength - offset - sizeof( Int32 ));
 285      }
 286
 287      internal AuthenticationRequestType RequestType { get; }
 288
 289      internal (Int32 offset, Int32 count) AdditionalDataInfo { get; }
 290   }
 291
 292   internal sealed class RowDescription : BackendMessageObject
 293   {
 294      internal sealed class FieldData
 295      {
 296         internal readonly String name;
 297         internal readonly Int32 tableID;
 298         internal readonly Int16 colAttr;
 299         internal readonly Int32 dataTypeID;
 300         internal readonly Int16 dataTypeSize;
 301         internal readonly Int32 dataTypeModifier;
 302
 303         internal FieldData( Byte[] array, Encoding encoding, ref Int32 offset )
 304         {
 305            this.name = array.ReadZeroTerminatedStringFromBytes( ref offset, encoding );
 306            this.tableID = array.ReadPgInt32( ref offset );
 307            this.colAttr = array.ReadPgInt16( ref offset );
 308            this.dataTypeID = array.ReadPgInt32( ref offset );
 309            this.dataTypeSize = array.ReadPgInt16( ref offset );
 310            this.dataTypeModifier = array.ReadPgInt32( ref offset );
 311            this.DataFormat = (DataFormat) array.ReadPgInt16( ref offset );
 312         }
 313
 314         internal DataFormat DataFormat { get; }
 315      }
 316
 317      public RowDescription( Byte[] array, Encoding encoding )
 318         : base( BackendMessageCode.RowDescription )
 319      {
 320         var offset = 0;
 321         var fieldCount = array.ReadPgInt16Count( ref offset );
 322         var fields = new FieldData[Math.Max( 0, fieldCount )];
 323         for ( var i = 0; i < fieldCount; ++i )
 324         {
 325            fields[i] = new FieldData( array, encoding, ref offset );
 326         }
 327         this.Fields = fields;
 328      }
 329
 330      internal FieldData[] Fields { get; }
 331   }
 332
 333   internal sealed class ParameterDescription : BackendMessageObject
 334   {
 335
 336      public ParameterDescription( Byte[] array )
 337         : base( BackendMessageCode.ParameterDescription )
 338      {
 339         var offset = 0;
 340         var idCount = array.ReadPgInt16Count( ref offset );
 341         var ids = new Int32[Math.Max( 0, idCount )];
 342         for ( var i = 0; i < idCount; ++i )
 343         {
 344            ids[i] = array.ReadPgInt32( ref offset );
 345         }
 346         this.ObjectIDs = ids;
 347
 348      }
 349
 350      internal Int32[] ObjectIDs { get; }
 351   }
 352
 353   internal sealed class ParameterStatus : BackendMessageObject
 354   {
 355
 356      public ParameterStatus( Byte[] array, Encoding encoding )
 357         : base( BackendMessageCode.ParameterStatus )
 358      {
 359         var offset = 0;
 360         this.Name = array.ReadZeroTerminatedStringFromBytes( ref offset, encoding );
 361         this.Value = array.ReadZeroTerminatedStringFromBytes( ref offset, encoding );
 362      }
 363
 364      internal String Name { get; }
 365
 366      internal String Value { get; }
 367   }
 368
 369   internal sealed class DataRowObject : BackendMessageObject
 370   {
 371      private readonly Int32 _columnCount;
 372      private readonly Transformable<Int32?, Int32>[] _columnSizes;
 373
 374      private DataRowObject(
 375         Int32 columnCount,
 376         ResizableArray<ResettableTransformable<Int32?, Int32>> columnSizes
 377         )
 378         : base( BackendMessageCode.DataRow )
 379      {
 380         this._columnCount = columnCount;
 381         columnSizes.CurrentMaxCapacity = columnCount;
 382         var array = columnSizes.Array;
 383         this._columnSizes = array;
 384         for ( var i = 0; i < columnCount; ++i )
 385         {
 386            var cur = array[i];
 387            if ( cur == null )
 388            {
 389               cur = new ResettableTransformable<Int32?, Int32>( null );
 390               array[i] = cur;
 391            }
 392            cur.TryReset();
 393         }
 394      }
 395
 396      public async Task<Int32> ReadColumnByteCount(
 397         BackendABIHelper args,
 398         Stream stream,
 399         CancellationToken token,
 400         Int32 columnIndex,
 401         ResizableArray<Byte> array
 402         )
 403      {
 404         var columnSizeHolder = this._columnSizes[columnIndex];
 405         await columnSizeHolder.TryTransitionOrWaitAsync( async unused =>
 406         {
 407            await stream.ReadSpecificAmountAsync( array.Array, 0, sizeof( Int32 ), token );
 408            var idx = 0;
 409            return array.Array.ReadPgInt32( ref idx );
 410         } );
 411         return columnSizeHolder.Transformed;
 412      }
 413
 414      public static async ValueTask<(DataRowObject, Int32)> ReadDataRow(
 415         Stream stream,
 416         CancellationToken token,
 417         Byte[] array,
 418         ResizableArray<ResettableTransformable<Int32?, Int32>> columnSizes,
 419         Int32 msgSize
 420         )
 421      {
 422         await stream.ReadSpecificAmountAsync( array, 0, sizeof( Int16 ), token );
 423         msgSize -= sizeof( Int16 );
 424         var idx = 0;
 425         var colCount = array.ReadPgInt16Count( ref idx );
 426         return (new DataRowObject( colCount, columnSizes ), msgSize);
 427      }
 428   }
 429
 430   internal sealed class ReadyForQuery : BackendMessageObject
 431   {
 432
 433      public ReadyForQuery( Byte[] array )
 434         : base( BackendMessageCode.ReadyForQuery )
 435      {
 436         this.Status = (TransactionStatus) array[0];
 437      }
 438
 439      public TransactionStatus Status { get; }
 440   }
 441
 442   internal sealed class BackendKeyData : BackendMessageObject
 443   {
 444
 445      public BackendKeyData( Byte[] array )
 446         : base( BackendMessageCode.BackendKeyData )
 447      {
 448         var idx = 0;
 449         this.ProcessID = array.ReadPgInt32( ref idx );
 450         this.Key = array.ReadPgInt32( ref idx );
 451      }
 452
 453      internal Int32 ProcessID { get; }
 454
 455      internal Int32 Key { get; }
 456   }
 457
 458   internal sealed class CommandComplete : BackendMessageObject
 459   {
 460
 461      public CommandComplete( Byte[] array, Encoding encoding )
 462         : base( BackendMessageCode.CommandComplete )
 463      {
 464         const String INSERT = "INSERT";
 465
 466         var idx = 0;
 467         var tag = array.ReadZeroTerminatedStringFromBytes( ref idx, encoding );
 468         this.FullCommandTag = tag;
 469         idx = 0;
 470         while ( Char.IsWhiteSpace( tag[idx] ) && ++idx < tag.Length ) ;
 471         String actualTag = null;
 472         if ( idx < tag.Length - 1 )
 473         {
 474            var start = idx;
 475            var max = idx;
 476            while ( !Char.IsWhiteSpace( tag[max] ) && ++max < tag.Length ) ;
 477            var isInsert = max - idx == INSERT.Length && tag.IndexOf( INSERT, StringComparison.OrdinalIgnoreCase ) == id
 478            if ( isInsert )
 479            {
 480               // Next word is inserted row id
 481               idx = max + 1;
 482               while ( Char.IsWhiteSpace( tag[idx] ) && ++idx < tag.Length ) ;
 483               max = idx;
 484               while ( !Char.IsWhiteSpace( tag[max] ) && ++max < tag.Length ) ;
 485               if ( max - idx > 0 && Int64.TryParse( tag.Substring( idx, max - idx ), out Int64 insertedID ) )
 486               {
 487                  this.LastInsertedID = insertedID;
 488               }
 489            }
 490
 491            // Last word is affected row count
 492            max = tag.Length - 1;
 493            while ( Char.IsWhiteSpace( tag[max] ) && --max >= 0 ) ;
 494            if ( max > 0 )
 495            {
 496               idx = max;
 497               ++max;
 498               while ( !Char.IsWhiteSpace( tag[idx] ) && --idx >= 0 ) ;
 499               ++idx;
 500               if ( max - idx > 0 && Int32.TryParse( tag.Substring( idx, max - idx ), out Int32 affectedRows ) )
 501               {
 502                  this.AffectedRows = affectedRows;
 503               }
 504
 505               // First integer word marks actual command id
 506               if ( this.AffectedRows.HasValue )
 507               {
 508                  // See if previous word is number (happens only in insert)
 509                  --idx;
 510                  Char c;
 511                  while ( ( Char.IsDigit( ( c = tag[idx] ) ) || c == '-' || c == '+' || Char.IsWhiteSpace( c ) ) && --id
 512                  if ( idx >= 0 )
 513                  {
 514                     actualTag = tag.Substring( 0, idx + 1 );
 515                  }
 516               }
 517            }
 518
 519         }
 520
 521         this.CommandTag = actualTag ?? tag;
 522
 523      }
 524
 525      internal String CommandTag { get; }
 526
 527      internal String FullCommandTag { get; }
 528
 529      internal Int32? AffectedRows { get; }
 530
 531      internal Int64? LastInsertedID { get; }
 532   }
 533
 534   internal sealed class NotificationMessage : BackendMessageObject
 535   {
 536      private readonly NotificationEventArgs _args;
 537
 538      public NotificationMessage( Byte[] array, Encoding encoding )
 539         : base( BackendMessageCode.NotificationResponse )
 540      {
 541         var idx = 0;
 542         this._args = new NotificationEventArgs(
 543            array.ReadPgInt32( ref idx ),
 544            array.ReadZeroTerminatedStringFromBytes( ref idx, encoding ),
 545            array.ReadZeroTerminatedStringFromBytes( ref idx, encoding )
 546            );
 547      }
 548
 549      internal NotificationEventArgs Args
 550      {
 551         get
 552         {
 553            return this._args;
 554         }
 555      }
 556   }
 557
 558   internal sealed class CopyInOrOutMessage : BackendMessageObject
 559   {
 560
 561      public CopyInOrOutMessage( Byte[] array, Boolean isIn )
 562         : base( isIn ? BackendMessageCode.CopyInResponse : BackendMessageCode.CopyOutResponse )
 563      {
 564         var idx = 0;
 565         this.CopyFormat = (DataFormat) array.ReadByteFromBytes( ref idx );
 566         var arraySize = array.ReadPgInt16Count( ref idx );
 567         var formats = new Int16[Math.Max( 0, arraySize )];
 568         for ( var i = 0; i < arraySize; ++i )
 569         {
 570            formats[i] = array.ReadPgInt16( ref idx );
 571         }
 572         this.FieldFormats = formats;
 573      }
 574
 575      internal DataFormat CopyFormat { get; }
 576
 577      internal Int16[] FieldFormats { get; }
 578   }
 579
 580   internal sealed class CopyDataMessage : BackendMessageObject
 581   {
 582      public CopyDataMessage( Int32 messageLength )
 583         : base( BackendMessageCode.CopyData )
 584      {
 585         this.DataSize = messageLength - 4;
 586      }
 587
 588      public Int32 DataSize { get; }
 589   }
 590
 591   internal sealed class MessageWithNoContents : BackendMessageObject
 592   {
 593      public static MessageWithNoContents PARSE_COMPLETE = new MessageWithNoContents( BackendMessageCode.ParseComplete )
 594      public static MessageWithNoContents BIND_COMPLETE = new MessageWithNoContents( BackendMessageCode.BindComplete );
 595      public static MessageWithNoContents CLOSE_COMPLETE = new MessageWithNoContents( BackendMessageCode.CloseComplete )
 596      public static MessageWithNoContents EMPTY_QUERY = new MessageWithNoContents( BackendMessageCode.EmptyQueryResponse
 597      public static MessageWithNoContents NO_DATA = new MessageWithNoContents( BackendMessageCode.NoData );
 598      public static MessageWithNoContents COPY_DONE = new MessageWithNoContents( BackendMessageCode.CopyDone );
 599
 600      private MessageWithNoContents( BackendMessageCode code )
 601         : base( code )
 602      {
 603
 604      }
 605   }
 606
 607
 608   internal enum BackendMessageCode : byte
 609   {
 610      ParseComplete = (Byte) '1',
 611      BindComplete = (Byte) '2',
 612      CloseComplete = (Byte) '3',
 613
 614      NotificationResponse = (Byte) 'A',
 615      CommandComplete = (Byte) 'C',
 616      DataRow = (Byte) 'D',
 617      ErrorResponse = (Byte) 'E',
 618      CopyInResponse = (Byte) 'G',
 619      CopyOutResponse = (Byte) 'H',
 620      EmptyQueryResponse = (Byte) 'I',
 621      BackendKeyData = (Byte) 'K',
 622      NoticeResponse = (Byte) 'N',
 623      AuthenticationRequest = (Byte) 'R',
 624      ParameterStatus = (Byte) 'S',
 625      RowDescription = (Byte) 'T',
 626      FunctionCallResponse = (Byte) 'V',
 627      ReadyForQuery = (Byte) 'Z',
 628
 629      CopyDone = (Byte) 'c',
 630      CopyData = (Byte) 'd',
 631      NoData = (Byte) 'n',
 632      PortalSuspended = (Byte) 's', // We should never get this message, as we always specify to fetch all rows in Execu
 633      ParameterDescription = (Byte) 't',
 634   }
 635}
 636
 637public static partial class E_CBAM
 638{
 639   internal static Int16 ReadPgInt16( this Byte[] array, ref Int32 index )
 640   {
 320641      return array.ReadInt16BEFromBytes( ref index );
 642   }
 643
 644   internal static Int32 ReadPgInt16Count( this Byte[] array, ref Int32 index )
 645   {
 252646      return array.ReadUInt16BEFromBytes( ref index );
 647   }
 648}

/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      {
 45         var typeInfo = PostgreSQLProtocol.CreateDefaultTypesInfos()
 46            .ToDictionary( tInfo => tInfo.Item1, tInfo => tInfo );
 47
 48         _DefaultTypes = new Dictionary<String, TypeFunctionalityInfo>()
 49         {
 50            { "text", (typeInfo[typeof(String)], true) },
 51            { "void", (typeInfo[typeof(String)], false) },
 52            { "char", (typeInfo[typeof(String)], false) },
 53            { "varchar", (typeInfo[typeof(String)], false) },
 54            { "bool", (typeInfo[typeof(Boolean)], true) },
 55            { "int2", (typeInfo[typeof(Int16)], true) },
 56            { "int4", (typeInfo[typeof(Int32)], true) },
 57            { "int8", (typeInfo[typeof(Int64)], true) },
 58            { "oid", (typeInfo[typeof(Int32)], false) },
 59            { "float4", (typeInfo[typeof(Single)], true) },
 60            { "float8", (typeInfo[typeof(Double)], true) },
 61            { "numeric", (typeInfo[typeof(Decimal)], true) },
 62            //{ "inet", typeof(PgSQLInternetAddress) },
 63            //{ "macaddr", typeof(PgSQLMacAddress) },
 64            { "money", (typeInfo[typeof(Decimal)], false) },
 65            { "uuid", (typeInfo[typeof(Guid)], true) },
 66            { "xml", (typeInfo[typeof(System.Xml.Linq.XElement)], true) },
 67            { "interval", (typeInfo[typeof(PgSQLInterval)], true) },
 68            { "date", (typeInfo[typeof(PgSQLDate)], true) },
 69            { "time", (typeInfo[typeof(PgSQLTime)], true) },
 70            { "timetz", (typeInfo[typeof(PgSQLTimeTZ)], true) },
 71            { "timestamp", (typeInfo[typeof(PgSQLTimestamp)], true) },
 72            { "timestamptz", (typeInfo[typeof(PgSQLTimestampTZ)], true) },
 73            { "abstime", (typeInfo[typeof(PgSQLTimestampTZ)], false) },
 74            { "bytea", (typeInfo[typeof(Byte[])], true) }
 75
 76         };
 77         _StaticTypeCache = new Dictionary<Version, TStaticTypeCacheValue>();
 78      }
 79
 80      private async Task ReadTypesFromServer( Boolean force, CancellationToken token )
 81      {
 82         var serverVersion = this._serverVersion;
 83         var typeCache = _StaticTypeCache;
 84         if ( force || !typeCache.TryGetValue( serverVersion, out TStaticTypeCacheValue types ) )
 85         {
 86            this.CurrentCancellationToken = token;
 87            try
 88            {
 89               types = await this.TypeRegistry.ReadTypeDataFromServer( _DefaultTypes.Keys );
 90            }
 91            finally
 92            {
 93               this.ResetCancellationToken();
 94            }
 95
 96            lock ( typeCache )
 97            {
 98               typeCache[serverVersion] = types;
 99            }
 100         }
 101
 102         this.TypeRegistry.AssignTypeData(
 103            types,
 104            tName => _DefaultTypes[tName].Item1.Item1,
 105            tuple =>
 106            {
 107               var valTuple = _DefaultTypes[tuple.DBTypeName];
 108               return new TypeFunctionalityCreationResult( valTuple.Item1.Item2, valTuple.Item2 );
 109            } );
 110      }
 111
 112      private static IEnumerable<(Type, PgSQLTypeFunctionality)> CreateDefaultTypesInfos()
 113      {
 114         // Assumes that string is non-empty
 115         Boolean ArrayElementStringNeedsQuotesSingleDelimiter( PgSQLTypeDatabaseData boundData, String value )
 116         {
 117            Boolean needsQuotes = false;
 118            var delimChar = boundData.ArrayDelimiter[0];
 119            for ( var i = 0; i < value.Length && !needsQuotes; ++i )
 120            {
 121               var c = value[i];
 122               switch ( c )
 123               {
 124                  case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 125                  case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 126                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 127                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 128                     needsQuotes = true;
 129                     break;
 130                  default:
 131                     if ( c == delimChar || Char.IsWhiteSpace( c ) )
 132                     {
 133                        needsQuotes = true;
 134                     }
 135                     break;
 136               }
 137            }
 138
 139            return needsQuotes;
 140         }
 141
 142         Boolean ArrayElementStringNeedsQuotesMultiDelimiter( PgSQLTypeDatabaseData boundData, String value )
 143         {
 144            var needsQuotes = value.IndexOf( boundData.ArrayDelimiter ) >= 0;
 145            if ( !needsQuotes )
 146            {
 147               for ( var i = 0; i < value.Length && !needsQuotes; ++i )
 148               {
 149                  var c = value[i];
 150                  switch ( c )
 151                  {
 152                     case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 153                     case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 154                     case PgSQLTypeFunctionalityForArrays.ESCAPE:
 155                     case PgSQLTypeFunctionalityForArrays.QUOTE:
 156                        needsQuotes = true;
 157                        break;
 158                     default:
 159                        if ( Char.IsWhiteSpace( c ) )
 160                        {
 161                           needsQuotes = true;
 162                        }
 163                        break;
 164                  }
 165               }
 166            }
 167            return needsQuotes;
 168         }
 169
 170         Int32 CalculateArrayElementStringSizeSingleDelimiter( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, 
 171         {
 172            var asciiByteSize = encoding.BytesPerASCIICharacter;
 173            var retVal = 2 * asciiByteSize + encoding.Encoding.GetByteCount( value );
 174            var delimChar = boundData.ArrayDelimiter[0];
 175            for ( var i = 0; i < value.Length; ++i )
 176            {
 177               var c = value[i];
 178               switch ( c )
 179               {
 180                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 181                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 182                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 183                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 184                     retVal += asciiByteSize;
 185                     break;
 186                     //default:
 187                     //   if ( delimChar == c || Char.IsWhiteSpace( c ) )
 188                     //   {
 189                     //      retVal += asciiByteSize;
 190                     //   }
 191                     //   break;
 192               }
 193            }
 194            return retVal;
 195         }
 196
 197         Int32 CalculateArrayElementStringSizeMultiDelimiter( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, S
 198         {
 199            var asciiByteSize = encoding.BytesPerASCIICharacter;
 200            var retVal = 2 * asciiByteSize + encoding.Encoding.GetByteCount( value ) + asciiByteSize * value.CountOccurr
 201            for ( var i = 0; i < value.Length; ++i )
 202            {
 203               var c = value[i];
 204               switch ( c )
 205               {
 206                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 207                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 208                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 209                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 210                     retVal += asciiByteSize;
 211                     break;
 212                     //default:
 213                     //   if ( Char.IsWhiteSpace( c ) )
 214                     //   {
 215                     //      retVal += asciiByteSize;
 216                     //   }
 217                     //   break;
 218               }
 219            }
 220            return retVal;
 221         }
 222
 223         void WriteArrayElementStringSingleDelimiter( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, Byte[] a
 224         {
 225            var prevIdx = 0;
 226            var delimChar = boundData.ArrayDelimiter[0];
 227            var encoding = helper.Encoding.Encoding;
 228            for ( var i = 0; i < value.Length; ++i )
 229            {
 230               var c = value[i];
 231               switch ( c )
 232               {
 233                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 234                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 235                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 236                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 237                     offset += encoding.GetBytes( value, prevIdx, i - prevIdx, array, offset );
 238                     helper.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.ESCAPE );
 239                     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
 253            offset += encoding.GetBytes( value, prevIdx, value.Length - prevIdx, array, offset );
 254         }
 255
 256         void WriteArrayElementStringMultiDelimiter( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, Byte[] ar
 257         {
 258            var prevIdx = 0;
 259            var encoding = helper.Encoding.Encoding;
 260            var delimCharStart = boundData.ArrayDelimiter[0];
 261            var delimCharEnd = boundData.ArrayDelimiter[1];
 262            for ( var i = 0; i < value.Length; ++i )
 263            {
 264               var c = value[i];
 265               switch ( c )
 266               {
 267                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_START:
 268                  //case (Char) PgSQLTypeFunctionalityForArrays.ARRAY_END:
 269                  case PgSQLTypeFunctionalityForArrays.ESCAPE:
 270                  case PgSQLTypeFunctionalityForArrays.QUOTE:
 271                     offset += encoding.GetBytes( value, prevIdx, i - prevIdx, array, offset );
 272                     helper.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.ESCAPE );
 273                     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
 292            offset += encoding.GetBytes( value, prevIdx, value.Length - prevIdx, array, offset );
 293         }
 294
 295
 296         // String
 297         yield return CreateSingleBodyInfoWithType(
 298         ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 299            {
 300               return args.GetStringWithPool( array, offset, count );
 301            },
 302            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 303            {
 304               return args.GetStringWithPool( array, offset, count );
 305            },
 306            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, String value, Boolean isArrayElement ) =>
 307            {
 308               EitherOr<Int32, String> retVal;
 309               if ( isArrayElement )
 310               {
 311                  // From https://www.postgresql.org/docs/current/static/arrays.html :
 312                  // The array output routine will put double quotes around element values if they are empty strings, co
 313                  // Assumes that value is non-empty
 314                  var isNullString = String.Equals( value, "null", StringComparison.OrdinalIgnoreCase );
 315                  var isSingleDelimiter = boundData.ArrayDelimiter.Length == 1;
 316                  if (
 317                     value.Length == 0
 318                     || isNullString
 319                     || ( isSingleDelimiter ? ArrayElementStringNeedsQuotesSingleDelimiter( boundData, value ) : ArrayEl
 320                  )
 321                  {
 322                     retVal = isNullString ?
 323                        6 * encoding.BytesPerASCIICharacter :
 324                        ( isSingleDelimiter ?
 325                           CalculateArrayElementStringSizeSingleDelimiter( boundData, encoding, value ) :
 326                           CalculateArrayElementStringSizeMultiDelimiter( boundData, encoding, value )
 327                        );
 328                  }
 329                  else
 330                  {
 331                     // We can serialize without quotes
 332                     retVal = value;
 333                  }
 334
 335               }
 336               else
 337               {
 338                  retVal = value;
 339               }
 340
 341               return retVal;
 342            },
 343            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, String value, Boolean isArrayElement ) =>
 344            {
 345               return encoding.Encoding.GetByteCount( value );
 346            },
 347            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, String value, TSyncTex
 348            {
 349               if ( sizeInfo.Item2 == null )
 350               {
 351                  // Because of how text format size is calculated, and because we are handling string type, we will com
 352
 353                  // Write starting quote
 354                  args.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 355
 356                  // Write content, and escape any double quotes and backslashes
 357                  if ( boundData.ArrayDelimiter.Length == 1 )
 358                  {
 359                     WriteArrayElementStringSingleDelimiter( boundData, args, array, value, ref offset );
 360                  }
 361                  else
 362                  {
 363                     WriteArrayElementStringMultiDelimiter( boundData, args, array, value, ref offset );
 364                  }
 365
 366                  // Write ending quote
 367                  args.Encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 368               }
 369               else
 370               {
 371                  offset += args.Encoding.Encoding.GetBytes( value, 0, value.Length, array, offset );
 372               }
 373               System.Diagnostics.Debug.Assert( offset == sizeInfo.Item1 );
 374            },
 375            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, String value, Int32 si
 376            {
 377               args.Encoding.Encoding.GetBytes( value, 0, value.Length, array, offset );
 378            },
 379            null,
 380            null
 381            );
 382
 383         // Boolean
 384         yield return CreateSingleBodyInfoWithType(
 385            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 386            {
 387               Byte b;
 388               return count > 0
 389                  && (
 390                     ( b = args.Encoding.ReadASCIIByte( array, ref offset ) ) == 'T'
 391                     || b == 't'
 392                     );
 393            },
 394            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 395            {
 396               return count > 0 && array[offset] != 0;
 397            },
 398            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Boolean value, Boolean isArrayElement ) =>
 399            {
 400               return encoding.BytesPerASCIICharacter;
 401            },
 402            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Boolean value, Boolean isArrayElement ) =>
 403            {
 404               return 1;
 405            },
 406            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Boolean value, TSyncTe
 407            {
 408               args.Encoding.WriteASCIIByte( array, ref offset, (Byte) ( value ? 't' : 'f' ) );
 409            },
 410            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Boolean value, Int32 s
 411            {
 412               array[offset] = (Byte) ( value ? 1 : 0 );
 413            },
 414            null,
 415            null
 416            );
 417
 418         // Int16
 419         yield return CreateSingleBodyInfoWithType(
 420            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 421            {
 422               return (Int16) args.Encoding.ParseInt32Textual( array, ref offset, (count / args.Encoding.BytesPerASCIICh
 423            },
 424            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 425            {
 426               return array.ReadInt16BEFromBytes( ref offset );
 427            },
 428            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int16 value, Boolean isArrayElement ) =>
 429            {
 430               return encoding.GetTextualIntegerRepresentationSize( value );
 431            },
 432            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int16 value, Boolean isArrayElement ) =>
 433            {
 434               return sizeof( Int16 );
 435            },
 436            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int16 value, TSyncText
 437            {
 438               args.Encoding.WriteIntegerTextual( array, ref offset, value );
 439            },
 440            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int16 value, Int32 siz
 441            {
 442               array.WriteInt16BEToBytes( ref offset, value );
 443            },
 444            null,
 445            null
 446            );
 447
 448         // Int32
 449         yield return CreateSingleBodyInfoWithType(
 450            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 451            {
 452               return args.Encoding.ParseInt32Textual( array, ref offset, (count / args.Encoding.BytesPerASCIICharacter,
 453            },
 454            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 455            {
 456               return array.ReadInt32BEFromBytes( ref offset );
 457            },
 458            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int32 value, Boolean isArrayElement ) =>
 459            {
 460               return encoding.GetTextualIntegerRepresentationSize( value );
 461            },
 462            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int32 value, Boolean isArrayElement ) =>
 463            {
 464               return sizeof( Int32 );
 465            },
 466            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 value, TSyncText
 467            {
 468               args.Encoding.WriteIntegerTextual( array, ref offset, value );
 469            },
 470            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 value, Int32 siz
 471            {
 472               array.WriteInt32BEToBytes( ref offset, value );
 473            },
 474            null,
 475            null
 476            );
 477
 478         // Int64
 479         yield return CreateSingleBodyInfoWithType(
 480            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 481            {
 482               return args.Encoding.ParseInt64Textual( array, ref offset, (count / args.Encoding.BytesPerASCIICharacter,
 483            },
 484            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 485            {
 486               return array.ReadInt64BEFromBytes( ref offset );
 487            },
 488            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int64 value, Boolean isArrayElement ) =>
 489            {
 490               return encoding.GetTextualIntegerRepresentationSize( value );
 491            },
 492            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Int64 value, Boolean isArrayElement ) =>
 493            {
 494               return sizeof( Int64 );
 495            },
 496            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int64 value, TSyncText
 497            {
 498               args.Encoding.WriteIntegerTextual( array, ref offset, value );
 499            },
 500            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int64 value, Int32 siz
 501            {
 502               array.WriteInt64BEToBytes( ref offset, value );
 503            },
 504            null,
 505            null
 506            );
 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
 512         yield return CreateSingleBodyInfoWithType(
 513            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 514            {
 515               return Single.Parse( args.GetStringWithPool( array, offset, count ), CommonPgSQLTypeFunctionalityInfo.Num
 516            },
 517            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 518            {
 519               return array.ReadSingleBEFromBytes( ref offset );
 520            },
 521            null,
 522            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Single value, Boolean isArrayElement ) =>
 523            {
 524               return sizeof( Single );
 525            },
 526            null,
 527            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Single value, Int32 si
 528            {
 529               array.WriteSingleBEToBytes( ref offset, value );
 530            },
 531            null,
 532            null
 533            );
 534
 535         // Double
 536         yield return CreateSingleBodyInfoWithType(
 537            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 538            {
 539               return Double.Parse( args.GetStringWithPool( array, offset, count ), CommonPgSQLTypeFunctionalityInfo.Num
 540            },
 541            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 542            {
 543               return array.ReadDoubleBEFromBytes( ref offset );
 544            },
 545            null,
 546            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Double value, Boolean isArrayElement ) =>
 547            {
 548               return sizeof( Double );
 549            },
 550            null,
 551            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Double value, Int32 si
 552            {
 553               array.WriteDoubleBEToBytes( ref offset, value );
 554            },
 555            null,
 556            null
 557            );
 558
 559         // Decimal
 560         yield return CreateSingleBodyInfoWithType(
 561            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 562            {
 563               return Decimal.Parse( args.GetStringWithPool( array, offset, count ), CommonPgSQLTypeFunctionalityInfo.Nu
 564            },
 565            null,
 566            null,
 567            null,
 568            null,
 569            null,
 570            null,
 571            null
 572            );
 573
 574         // Guid
 575         yield return CreateSingleBodyInfoWithType(
 576            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 577            {
 578               return Guid.Parse( args.GetStringWithPool( array, offset, count ) );
 579            },
 580            null,
 581            null,
 582            null,
 583            null,
 584            null,
 585            null,
 586            null
 587            );
 588
 589         // Xml
 590         yield return CreateSingleBodyInfoWithType(
 591            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 592            {
 593               // If there would be "LoadAsync" in XEelement, we could use new PgSQLTypeUnboundInfo constructor directly
 594               using ( var mStream = new System.IO.MemoryStream( array, offset, count, false ) )
 595               {
 596                  return System.Xml.Linq.XElement.Load( mStream );
 597               }
 598            },
 599            null,
 600            null,
 601            null,
 602            null,
 603            null,
 604            null,
 605            null
 606            );
 607
 608         // PgSQLInterval
 609         yield return CreateSingleBodyInfoWithType(
 610            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 611            {
 612               // TODO interval parsing without string allocation (PgSQLInterval.Load(Stream stream))
 613               // Or PgSQLInterval.ParseBinaryText
 614               return PgSQLInterval.Parse( args.GetStringWithPool( array, offset, count ) );
 615            },
 616            null,
 617            // TODO interval string writing without string allocation
 618            null,
 619            null,
 620            null,
 621            null,
 622            ( PgSQLTypeDatabaseData dbData, PgSQLInterval pgSQLObject, Type targetType ) =>
 623            {
 624               return (TimeSpan) pgSQLObject;
 625            },
 626            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 627            {
 628               return (TimeSpan) systemObject;
 629            }
 630            );
 631
 632         // PgSQLDate
 633         yield return CreateSingleBodyInfoWithType(
 634            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 635            {
 636               return PgSQLDate.ParseBinaryText( args.Encoding, array, ref offset, count );
 637            },
 638            null,
 639            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLDate value, Boolean isArrayElement ) =>
 640            {
 641               var retVal = value.GetTextByteCount( encoding );
 642               if ( isArrayElement && value.NeedsQuotingInArrayElement() )
 643               {
 644                  // The " BC" substring contains whitespace, so we must put quotes around this
 645                  retVal += 2 * encoding.BytesPerASCIICharacter;
 646               }
 647               return retVal;
 648            },
 649            null,
 650            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLDate value, TSync
 651            {
 652               value.WriteBytesAndPossiblyQuote( args.Encoding, array, ref offset, isArrayElement && value.NeedsQuotingI
 653            },
 654            null,
 655            ( PgSQLTypeDatabaseData dbData, PgSQLDate pgSQLObject, Type targetType ) =>
 656            {
 657               return (DateTime) pgSQLObject;
 658            },
 659            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 660            {
 661               return (PgSQLDate) (DateTime) systemObject;
 662            }
 663            );
 664
 665         // PgSQLTime
 666         yield return CreateSingleBodyInfoWithType(
 667            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 668            {
 669               return PgSQLTime.ParseBinaryText( args.Encoding, array, ref offset, count );
 670            },
 671            null,
 672            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTime value, Boolean isArrayElement ) =>
 673            {
 674               return value.GetTextByteCount( encoding );
 675            },
 676            null,
 677            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTime value, TSync
 678            {
 679               value.WriteTextBytes( args.Encoding, array, ref offset );
 680            },
 681            null,
 682            ( PgSQLTypeDatabaseData dbData, PgSQLTime pgSQLObject, Type targetType ) =>
 683            {
 684               if ( typeof( DateTime ).Equals( targetType ) )
 685               {
 686                  return (DateTime) pgSQLObject;
 687               }
 688               else if ( typeof( TimeSpan ).Equals( targetType ) )
 689               {
 690                  return (TimeSpan) pgSQLObject;
 691               }
 692               else if ( typeof( PgSQLInterval ).Equals( targetType ) )
 693               {
 694                  return (PgSQLInterval) pgSQLObject;
 695               }
 696               else
 697               {
 698                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 699               }
 700            },
 701            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 702            {
 703               var tt = systemObject.GetType();
 704               if ( typeof( DateTime ).Equals( tt ) )
 705               {
 706                  return (PgSQLTime) (DateTime) systemObject;
 707               }
 708               else if ( typeof( TimeSpan ).Equals( tt ) )
 709               {
 710                  return (PgSQLTime) (TimeSpan) systemObject;
 711               }
 712               else if ( typeof( PgSQLInterval ).Equals( tt ) )
 713               {
 714                  return (PgSQLTime) (PgSQLInterval) systemObject;
 715               }
 716               else
 717               {
 718                  throw new InvalidCastException( "Can't cast object " + systemObject + " to time." );
 719               }
 720            }
 721            );
 722
 723         // PgSQLTimeTZ
 724         yield return CreateSingleBodyInfoWithType(
 725            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 726            {
 727               return PgSQLTimeTZ.ParseBinaryText( args.Encoding, array, ref offset, count );
 728            },
 729            null,
 730            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTimeTZ value, Boolean isArrayElement ) =>
 731            {
 732               return value.GetTextByteCount( encoding );
 733            },
 734            null,
 735            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTimeTZ value, TSy
 736            {
 737               value.WriteTextBytes( args.Encoding, array, ref offset );
 738            },
 739            null,
 740            ( PgSQLTypeDatabaseData dbData, PgSQLTimeTZ pgSQLObject, Type targetType ) =>
 741            {
 742               if ( typeof( DateTime ).Equals( targetType ) )
 743               {
 744                  return (DateTime) pgSQLObject;
 745               }
 746               else if ( typeof( TimeSpan ).Equals( targetType ) )
 747               {
 748                  return (TimeSpan) pgSQLObject;
 749               }
 750               else
 751               {
 752                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 753               }
 754            },
 755            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 756            {
 757               var tt = systemObject.GetType();
 758               if ( typeof( DateTime ).Equals( tt ) )
 759               {
 760                  return (PgSQLTimeTZ) (DateTime) systemObject;
 761               }
 762               else if ( typeof( TimeSpan ).Equals( tt ) )
 763               {
 764                  return (PgSQLTimeTZ) (TimeSpan) systemObject;
 765               }
 766               else
 767               {
 768                  throw new InvalidCastException( "Can't cast object " + systemObject + " to time with time zone." );
 769               }
 770            }
 771            );
 772
 773         // PgSQLTimestamp
 774         yield return CreateSingleBodyInfoWithType(
 775            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 776            {
 777               return PgSQLTimestamp.ParseBinaryText( args.Encoding, array, ref offset, count );
 778            },
 779            null,
 780            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTimestamp value, Boolean isArrayElement ) =>
 781            {
 782               var retVal = value.GetTextByteCount( encoding );
 783               if ( isArrayElement && value.NeedsQuotingInArrayElement() )
 784               {
 785                  // There will be a space -> the value must be quoted
 786                  retVal += 2 * encoding.BytesPerASCIICharacter;
 787               }
 788               return retVal;
 789            },
 790            null,
 791            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTimestamp value, 
 792            {
 793               value.WriteBytesAndPossiblyQuote( args.Encoding, array, ref offset, isArrayElement && value.NeedsQuotingI
 794            },
 795            null,
 796            ( PgSQLTypeDatabaseData dbData, PgSQLTimestamp pgSQLObject, Type targetType ) =>
 797            {
 798               if ( typeof( DateTime ).Equals( targetType ) )
 799               {
 800                  return (DateTime) pgSQLObject;
 801               }
 802               else
 803               {
 804                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 805               }
 806            },
 807            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 808            {
 809               var tt = systemObject.GetType();
 810               if ( typeof( DateTime ).Equals( tt ) )
 811               {
 812                  return (PgSQLTimestamp) (DateTime) systemObject;
 813               }
 814               else
 815               {
 816                  throw new InvalidCastException( "Can't cast object " + systemObject + " to timestamp." );
 817               }
 818            }
 819            );
 820
 821         // PgSQLTimestampTZ
 822         yield return CreateSingleBodyInfoWithType(
 823            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 824            {
 825               return PgSQLTimestampTZ.ParseBinaryText( args.Encoding, array, ref offset, count );
 826            },
 827            null,
 828            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, PgSQLTimestampTZ value, Boolean isArrayElement ) 
 829            {
 830               var retVal = value.GetTextByteCount( encoding );
 831               if ( isArrayElement && value.NeedsQuotingInArrayElement() )
 832               {
 833                  retVal += 2 * encoding.BytesPerASCIICharacter;
 834               }
 835               return retVal;
 836            },
 837            null,
 838            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, PgSQLTimestampTZ value
 839            {
 840               value.WriteBytesAndPossiblyQuote( args.Encoding, array, ref offset, isArrayElement && value.NeedsQuotingI
 841            },
 842            null,
 843            ( PgSQLTypeDatabaseData dbData, PgSQLTimestampTZ pgSQLObject, Type targetType ) =>
 844            {
 845               if ( typeof( DateTime ).Equals( targetType ) )
 846               {
 847                  return (DateTime) pgSQLObject;
 848               }
 849               else if ( typeof( DateTimeOffset ).Equals( targetType ) )
 850               {
 851                  return (DateTimeOffset) pgSQLObject;
 852               }
 853               else
 854               {
 855                  throw new InvalidCastException( "Can't cast time " + pgSQLObject + " to " + targetType + "." );
 856               }
 857            },
 858            ( PgSQLTypeDatabaseData dbData, Object systemObject ) =>
 859            {
 860               var tt = systemObject.GetType();
 861               if ( typeof( DateTime ).Equals( tt ) )
 862               {
 863                  return (PgSQLTimestampTZ) (DateTime) systemObject;
 864               }
 865               else if ( typeof( DateTimeOffset ).Equals( tt ) )
 866               {
 867                  return (PgSQLTimestampTZ) (DateTimeOffset) systemObject;
 868               }
 869               else
 870               {
 871                  throw new InvalidCastException( "Can't cast object " + systemObject + " to timestamp with time zone." 
 872               }
 873            }
 874            );
 875
 876         // Byte[]
 877         yield return CreateSingleBodyInfoWithType(
 878            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 879            {
 880               // Byte[] textual format is "\x<hex decimal pairs>"
 881               var encoding = args.Encoding;
 882               Byte[] retVal;
 883               if ( count > 2
 884               && encoding.ReadASCIIByte( array, ref offset ) == '\\'
 885               && encoding.ReadASCIIByte( array, ref offset ) == 'x'
 886               )
 887               {
 888                  var len = ( count - offset ) / encoding.BytesPerASCIICharacter / 2;
 889                  retVal = new Byte[len];
 890                  for ( var i = 0; i < len; ++i )
 891                  {
 892                     retVal[i] = encoding.ReadHexDecimal( array, ref offset );
 893                  }
 894               }
 895               else
 896               {
 897                  throw new PgSQLException( "Bytea strings must start with \"\\x\"." );
 898               }
 899
 900               return retVal;
 901            },
 902            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Int32 count ) =>
 903            {
 904               // Binary format is just raw byte array
 905               var retVal = new Byte[count];
 906               Array.Copy( array, offset, retVal, 0, count );
 907               return retVal;
 908            },
 909            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Byte[] value, Boolean isArrayElement ) =>
 910            {
 911               // Text size is 2 ASCII bytes + 2 ASCII bytes per each actual byte
 912               var retVal = ( 2 + 2 * value.Length ) * encoding.BytesPerASCIICharacter;
 913               if ( isArrayElement )
 914               {
 915                  // Always need quotation since there is a '\\' character
 916                  retVal += 2 * encoding.BytesPerASCIICharacter;
 917               }
 918               return retVal;
 919            },
 920            ( PgSQLTypeDatabaseData boundData, IEncodingInfo encoding, Byte[] value, Boolean isArrayElement ) =>
 921            {
 922               // Binary size is same as array size
 923               return value.Length;
 924            },
 925            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Byte[] value, TSyncTex
 926            {
 927               // Write all text to array
 928               var encoding = args.Encoding;
 929               if ( isArrayElement )
 930               {
 931                  encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 932               }
 933
 934               encoding
 935                  .WriteASCIIByte( array, ref offset, (Byte) '\\' )
 936                                 .WriteASCIIByte( array, ref offset, (Byte) 'x' );
 937               foreach ( var b in value )
 938               {
 939                  encoding.WriteHexDecimal( array, ref offset, b );
 940               }
 941               if ( isArrayElement )
 942               {
 943                  encoding.WriteASCIIByte( array, ref offset, (Byte) PgSQLTypeFunctionalityForArrays.QUOTE );
 944               }
 945            },
 946            ( PgSQLTypeDatabaseData boundData, BackendABIHelper args, Byte[] array, Int32 offset, Byte[] value, Int32 ad
 947            {
 948               // Just copy array
 949               Array.Copy( value, 0, array, offset, value.Length );
 950            },
 951            null,
 952            null
 953            );
 954      }
 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      {
 967         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
 0979      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
 0985      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
 0991      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   {
 01005      if ( needsQuoting )
 1006      {
 01007         encoding.WriteASCIIByte( array, ref index, (Byte) '"' );
 1008      }
 01009      writeTextBytes( value, encoding, array, ref index );
 01010      if ( needsQuoting )
 1011      {
 01012         encoding.WriteASCIIByte( array, ref index, (Byte) '"' );
 1013      }
 01014   }
 1015}

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Writing.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.SQL.Implementation;
 20using CBAM.SQL.PostgreSQL;
 21using CBAM.SQL.PostgreSQL.Implementation;
 22using System;
 23using System.Collections.Generic;
 24using System.IO;
 25using System.Linq;
 26using System.Text;
 27using System.Threading;
 28using System.Threading.Tasks;
 29using UtilPack;
 30using BackendExtraSizeInfo = System.ValueTuple<CBAM.SQL.PostgreSQL.BackendSizeInfo, System.Object>;
 31using MessageIOArgs = System.ValueTuple<CBAM.SQL.PostgreSQL.BackendABIHelper, System.IO.Stream, System.Threading.Cancell
 32using FormatCodeInfo = System.ValueTuple<CBAM.SQL.PostgreSQL.DataFormat[], System.Int32>;
 33
 34namespace CBAM.SQL.PostgreSQL.Implementation
 35{
 36   internal abstract class FrontEndMessage
 37   {
 38      private const Int32 MAX_MESSAGE_SIZE = 0x3fffffff;
 39
 40      public async Task SendMessageAsync(
 41         MessageIOArgs args,
 42         Boolean skipFlush = false
 43         )
 44      {
 45         var size = this.CalculateSize( args.Item1, args.Item4 ) + 4;
 46         if ( size > MAX_MESSAGE_SIZE )
 47         {
 48            // Backend's maxalloc size is largest message size that it can allocate
 49            throw new PgSQLException( "Too big message to send to backend (max is " + MAX_MESSAGE_SIZE + ", and " + this
 50         }
 51
 52         try
 53         {
 54            await this.DoSendMessageAsync( args.Item1, args.Item2, size, args.Item3, args.Item4 );
 55            if ( !skipFlush )
 56            {
 57               await args.Item2.FlushAsync( args.Item3 );
 58            }
 59         }
 60         catch ( Exception exc )
 61         {
 62            throw new PgSQLException( "Error when writing message to backend.", exc );
 63         }
 64      }
 65
 66      protected abstract Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array );
 67
 68      protected abstract Task DoSendMessageAsync( BackendABIHelper args, Stream stream, Int32 size, CancellationToken to
 69
 70
 71   }
 72   internal abstract class FrontEndMessageWithCode : FrontEndMessage
 73   {
 74      private const Int32 PREFIX_SIZE = sizeof( Byte ) + sizeof( Int32 );
 75
 76      private readonly FrontEndMessageCode _code;
 77
 78      internal FrontEndMessageWithCode( FrontEndMessageCode code )
 79      {
 80         this._code = code;
 81      }
 82
 83      protected override async Task DoSendMessageAsync(
 84         BackendABIHelper args,
 85         Stream stream,
 86         Int32 size,
 87         CancellationToken token,
 88         ResizableArray<Byte> array
 89         )
 90      {
 91         array.CurrentMaxCapacity = PREFIX_SIZE;
 92         var idx = 0;
 93
 94         array.Array
 95            .WriteByteToBytes( ref idx, (Byte) this._code )
 96            .WritePgInt32( ref idx, size );
 97         await stream.WriteAsync( array.Array, 0, PREFIX_SIZE, token );
 98         await this.PerformSendAfterWriteAsync( args, stream, size, token, array );
 99      }
 100
 101      protected abstract Task PerformSendAfterWriteAsync( BackendABIHelper args, Stream stream, Int32 size, Cancellation
 102
 103      internal FrontEndMessageCode Code
 104      {
 105         get
 106         {
 107            return this._code;
 108         }
 109      }
 110   }
 111
 112   internal sealed class FrontEndMessageWithNoContent : FrontEndMessageWithCode
 113   {
 114      internal static readonly FrontEndMessageWithNoContent TERMINATION = new FrontEndMessageWithNoContent( FrontEndMess
 115      internal static readonly FrontEndMessageWithNoContent SYNC = new FrontEndMessageWithNoContent( FrontEndMessageCode
 116      internal static readonly FrontEndMessageWithNoContent FLUSH = new FrontEndMessageWithNoContent( FrontEndMessageCod
 117      internal static readonly FrontEndMessageWithNoContent COPY_DONE = new FrontEndMessageWithNoContent( FrontEndMessag
 118
 119      private FrontEndMessageWithNoContent( FrontEndMessageCode code )
 120         : base( code )
 121      {
 122      }
 123
 124      protected override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 125      {
 126         return 0;
 127      }
 128
 129      protected override Task PerformSendAfterWriteAsync( BackendABIHelper args, Stream stream, Int32 size, Cancellation
 130      {
 131         return TaskUtils.CompletedTask;
 132      }
 133   }
 134
 135   internal sealed class PasswordMessage : FrontEndMessageWithSingleBody
 136   {
 137      private readonly Byte[] _pw;
 138
 139      internal PasswordMessage( Byte[] pw )
 140         : base( FrontEndMessageCode.PasswordMessage )
 141      {
 142         ArgumentValidator.ValidateNotNull( "Password", pw );
 143
 144         this._pw = pw;
 145      }
 146
 147      protected override Int32 CalculateBufferSize( BackendABIHelper args, ResizableArray<Byte> array )
 148      {
 149         return this._pw.Length;
 150      }
 151
 152      protected override void WriteMessageToBuffer( BackendABIHelper args, ResizableArray<Byte> array )
 153      {
 154         this._pw.CopyTo( array.Array, 0 );
 155      }
 156   }
 157
 158   internal sealed class StartupMessage : FrontEndMessage
 159   {
 160      private const Int32 PREFIX = 8;
 161
 162      private readonly Int32 _protocolVersion;
 163      private readonly IDictionary<String, String> _parameters;
 164
 165      internal StartupMessage(
 166         Int32 protocolVersion,
 167         IDictionary<String, String> parameters
 168         )
 169      {
 170         this._protocolVersion = protocolVersion;
 171         this._parameters = parameters ?? new Dictionary<String, String>();
 172      }
 173
 174      protected override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 175      {
 176         return 5 // int32 (protocol version) + end-byte (after message, zero)
 177            + this._parameters.Sum( kvp => args.GetStringSize( kvp.Key, array ) + args.GetStringSize( kvp.Value, array )
 178      }
 179
 180      protected override async Task DoSendMessageAsync(
 181         BackendABIHelper args,
 182         Stream stream,
 183         Int32 size,
 184         CancellationToken token,
 185         ResizableArray<Byte> array
 186         )
 187      {
 188         // As documentation states, "For historical reasons, the very first message sent by the client (the startup mes
 189         // Hence we don't inherit the FrontEndMessageObjectWithCode class
 190         array.CurrentMaxCapacity = PREFIX;
 191         var idx = 0;
 192         array.Array
 193            .WritePgInt32( ref idx, size )
 194            .WritePgInt32( ref idx, this._protocolVersion );
 195         await stream.WriteAsync( array.Array, 0, PREFIX, token );
 196
 197         foreach ( var kvp in this._parameters )
 198         {
 199            await args.WriteString( stream, kvp.Key, token, array );
 200            await args.WriteString( stream, kvp.Value, token, array );
 201         }
 202         array.Array[0] = 0;
 203         await stream.WriteAsync( array.Array, 0, 1, token );
 204      }
 205   }
 206
 207   internal abstract class FrontEndMessageWithSingleBody : FrontEndMessageWithCode
 208   {
 209      public FrontEndMessageWithSingleBody( FrontEndMessageCode code )
 210         : base( code )
 211      {
 212      }
 213
 214      protected sealed override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 215      {
 216         return this.CalculateBufferSize( args, array );
 217      }
 218
 219      protected sealed override async Task PerformSendAfterWriteAsync(
 220         BackendABIHelper args,
 221         Stream stream,
 222         Int32 size,
 223         CancellationToken token,
 224         ResizableArray<Byte> array
 225         )
 226      {
 227         // Given size includes the integer that already has been written
 228         size -= sizeof( Int32 );
 229         array.CurrentMaxCapacity = size;
 230         this.WriteMessageToBuffer( args, array );
 231         await stream.WriteAsync( array.Array, 0, size, token );
 232      }
 233
 234      protected abstract Int32 CalculateBufferSize( BackendABIHelper args, ResizableArray<Byte> array );
 235
 236      protected abstract void WriteMessageToBuffer( BackendABIHelper args, ResizableArray<Byte> array );
 237   }
 238
 239   internal sealed class QueryMessage : FrontEndMessageWithSingleBody
 240   {
 241      private readonly String _query;
 242
 243      internal QueryMessage( String query )
 244         : base( FrontEndMessageCode.Query )
 245      {
 246         this._query = query;
 247      }
 248
 249      protected override Int32 CalculateBufferSize( BackendABIHelper args, ResizableArray<Byte> array )
 250      {
 251         return args.GetStringSize( this._query, array );
 252      }
 253
 254      protected override void WriteMessageToBuffer( BackendABIHelper args, ResizableArray<Byte> array )
 255      {
 256         var idx = 0;
 257         array.Array.WritePgString( ref idx, args.Encoding, this._query );
 258      }
 259   }
 260
 261   internal sealed class ParseMessage : FrontEndMessageWithCode
 262   {
 263      private readonly String _statementName;
 264      private readonly String _sql;
 265      private readonly Int32[] _typeIDs;
 266
 267      internal ParseMessage( String sql, Int32[] paramIndices, Int32[] typeIDs, String statementName = null )
 268         : base( FrontEndMessageCode.Parse )
 269      {
 270         ArgumentValidator.ValidateNotNull( "SQL", sql );
 271
 272         this._statementName = statementName;
 273         // Replace "blaa ? blaa2 ? blaa3" with "blaa $1 blaa2 $2 blaa3"
 274         var sb = new StringBuilder();
 275         var prev = 0;
 276         if ( paramIndices != null )
 277         {
 278            var curParam = 1;
 279            foreach ( var i in paramIndices )
 280            {
 281               sb.Append( sql.Substring( prev, i - prev ) )
 282               .Append( '$' ).Append( curParam++ );
 283               prev = i + 1;
 284            }
 285         }
 286
 287         sb.Append( sql.Substring( prev ) );
 288
 289         this._sql = sb.ToString();
 290         this._typeIDs = typeIDs;
 291      }
 292
 293      protected override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 294      {
 295         return args.GetStringSize( this._statementName, array )
 296            + args.GetStringSize( this._sql, array )
 297            + sizeof( Int16 )
 298            + this._typeIDs.GetLengthOrDefault() * sizeof( Int32 );
 299      }
 300
 301      protected override async Task PerformSendAfterWriteAsync(
 302         BackendABIHelper args,
 303         Stream stream,
 304         Int32 size,
 305         CancellationToken token,
 306         ResizableArray<Byte> buffer
 307         )
 308      {
 309         await args.WriteString( stream, this._statementName, token, buffer );
 310         await args.WriteString( stream, this._sql, token, buffer );
 311
 312         var typesCount = this._typeIDs.GetLengthOrDefault();
 313         buffer.CurrentMaxCapacity = sizeof( Int16 ) + 4 * typesCount;
 314         var array = buffer.Array;
 315         var idx = 0;
 316         array.WritePgInt16( ref idx, typesCount );
 317         if ( typesCount > 0 )
 318         {
 319            foreach ( var typeID in this._typeIDs )
 320            {
 321               array.WritePgInt32( ref idx, typeID );
 322            }
 323         }
 324         await stream.WriteAsync( array, 0, idx, token );
 325      }
 326
 327      // Too many task allocations to make this feasible, at least at the moment (maybe when ValueTask is visible in .NE
 328      // Or make this use new WritePgString method for arrays...
 329      //private async Task SendSQL(
 330      //   MessageSendingArgs args,
 331      //   Stream stream
 332      //   )
 333      //{
 334      //   // Replace "blaa ? blaa2 ? blaa3" with "blaa $1 blaa2 $2 blaa3"
 335      //   var paramIndices = this._paramIndices;
 336      //   // Send first chunk
 337      //   var sql = this._sql;
 338      //   await args.WriteStringPart( stream, sql, 0, paramIndices.Length > 0 ? paramIndices[0] : sql.Length );
 339
 340      //   for (var i = 0; i < paramIndices.Length; ++i )
 341      //   {
 342      //      // Send $<number>
 343      //      var idx = 0;
 344      //      args.Buffer.Array[0] = 0x24; // '$'
 345      //      args.Buffer.Array.WritePgIntTextual( ref idx, i );
 346      //      await stream.WriteAsync( args.Buffer.Array, 0, idx );
 347      //      // Send next chunk
 348      //      var nextStartIndex = paramIndices[i] + 1;
 349      //      var nextEndIndex = i < paramIndices.Length - 1 ? paramIndices[i + 1] : sql.Length;
 350      //      await args.WriteStringPart( stream, sql, nextStartIndex, nextEndIndex - nextStartIndex );
 351      //   }
 352
 353      //   // Send terminating zero
 354      //   await args.WriteString( stream, null );
 355      // }
 356   }
 357
 358   internal sealed class CloseMessage : FrontEndMessageWithSingleBody
 359   {
 360      internal static readonly CloseMessage UNNAMED_STATEMENT = new CloseMessage( true );
 361      internal static readonly CloseMessage UNNAMED_PORTAL = new CloseMessage( false );
 362
 363      private readonly Boolean _isStatement;
 364      private readonly String _name;
 365
 366      internal CloseMessage( Boolean isStatement, String name = null )
 367         : base( FrontEndMessageCode.Close )
 368      {
 369         this._isStatement = isStatement;
 370         this._name = name;
 371      }
 372
 373      protected override Int32 CalculateBufferSize( BackendABIHelper args, ResizableArray<Byte> array )
 374      {
 375         return 1 + args.GetStringSize( this._name, array );
 376      }
 377
 378      protected override void WriteMessageToBuffer( BackendABIHelper args, ResizableArray<Byte> array )
 379      {
 380         var idx = 0;
 381         array.Array
 382            .WriteByteToBytes( ref idx, (Byte) ( this._isStatement ? 'S' : 'P' ) )
 383            .WritePgString( ref idx, args.Encoding, this._name );
 384      }
 385   }
 386
 387
 388   internal sealed class BindMessage : FrontEndMessageWithCode
 389   {
 390      private readonly String _portalName;
 391      private readonly String _statementName;
 392      private readonly IEnumerable<StatementParameter> _params;
 393      private readonly TypeFunctionalityInformation[] _types;
 394      private readonly BackendExtraSizeInfo[] _preCreatedParamSizes;
 395      private readonly Boolean _disableBinarySend;
 396      private readonly Boolean _disableBinaryReceive;
 397      private readonly FormatCodeInfo _sendCodes;
 398      //private readonly FormatCodeInfo _receiveCodes;
 399
 400      internal BindMessage(
 401         IEnumerable<StatementParameter> paramz,
 402         Int32 paramCount,
 403         TypeFunctionalityInformation[] types,
 404         Boolean disableBinarySend,
 405         Boolean disableBinaryReceive,
 406         String portalName = null,
 407         String statementName = null
 408         )
 409         : base( FrontEndMessageCode.Bind )
 410      {
 411         this._portalName = portalName;
 412         this._statementName = statementName;
 413         this._disableBinarySend = disableBinarySend;
 414         this._disableBinaryReceive = disableBinaryReceive;
 415         this._params = paramz;
 416         this._types = types;
 417         this._preCreatedParamSizes = new BackendExtraSizeInfo[paramCount];
 418         this._sendCodes = this.GetFormatCodes( true );
 419         //this._receiveCodes = this.GetFormatCodes( false );
 420      }
 421
 422      protected override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 423      {
 424         var retVal = args.GetStringSize( this._portalName, array )
 425            + args.GetStringSize( this._statementName, array )
 426            + 6 // paramFormatCount, paramCount, columnFormatCount
 427            + this._sendCodes.Item2 * sizeof( Int16 )
 428            //+ this._receiveCodes.Item2 * sizeof( Int16 )
 429            + this.CalculateParamSizes( args );
 430         return retVal;
 431      }
 432
 433      private Int32 CalculateParamSizes( BackendABIHelper args )
 434      {
 435         var paramCount = this._preCreatedParamSizes.Length;
 436         var size = 4 * paramCount; // Each parameter takes at least 4 bytes
 437         var i = 0;
 438         var formatCodes = this._sendCodes.Item1;
 439         foreach ( var param in this._params )
 440         {
 441            var val = param.ParameterValue;
 442            if ( val != null )
 443            {
 444               var thisType = this._types[i];
 445               // Change type if needed
 446               if ( !thisType.CLRType.Equals( val.GetType() ) )
 447               {
 448                  val = thisType.Functionality.ChangeTypeFrameworkToPgSQL( thisType.DatabaseData, val );
 449               }
 450
 451               var thisFormat = formatCodes == null ? DataFormat.Text : formatCodes[i >= formatCodes.Length ? 0 : i];
 452               var thisSizeInfo = thisType.Functionality.GetBackendSize( thisFormat, thisType.DatabaseData, args, val, f
 453
 454               this._preCreatedParamSizes[i] = (thisSizeInfo, val);
 455
 456
 457               size += thisSizeInfo.ByteCount;
 458            }
 459
 460            ++i;
 461         }
 462         return size;
 463      }
 464
 465      protected override async Task PerformSendAfterWriteAsync(
 466         BackendABIHelper args,
 467         Stream stream,
 468         Int32 size,
 469         CancellationToken token,
 470         ResizableArray<Byte> array
 471         )
 472      {
 473
 474         // Start building message
 475         await args.WriteString( stream, this._portalName, token, array );
 476         await args.WriteString( stream, this._statementName, token, array );
 477
 478         // Write format info about parameters
 479         var formatCodes = await this.WriteFormatInfo( args, stream, token, true, array );
 480
 481         // Write parameters
 482         var idx = 0;
 483         array.Array.WritePgInt16( ref idx, this._preCreatedParamSizes.Length );
 484         await stream.WriteAsync( array.Array, 0, idx, token );
 485         for ( var i = 0; i < this._preCreatedParamSizes.Length; ++i )
 486         {
 487            var thisSizeTuple = this._preCreatedParamSizes[i];
 488            var thisType = this._types[i];
 489            var thisStream = StreamFactory.CreateLimitedWriter(
 490               stream,
 491               thisSizeTuple.Item1.ByteCount + sizeof( Int32 ),
 492               token,
 493               array
 494               );
 495            try
 496            {
 497               await thisType.Functionality.WriteBackendValueCheckNull(
 498                  formatCodes == null ? DataFormat.Text : formatCodes[i >= formatCodes.Length ? 0 : i],
 499                  thisType.DatabaseData,
 500                  args,
 501                  thisStream,
 502                  thisSizeTuple.Item2,
 503                  thisSizeTuple.Item1,
 504                  false
 505                  );
 506
 507
 508            }
 509            finally
 510            {
 511               await thisStream.FlushAsync();
 512            }
 513         }
 514
 515         // Write format info for columns
 516         // Since we don't know the result column count (that'd require advanced SQL parsing), just set all formats to t
 517         // TODO add ResultColumnInfo: Type[] to StatementBuilder, which would be used for prepared statements, which wo
 518         idx = 0;
 519         array.Array.WritePgInt16( ref idx, 0 );
 520         await stream.WriteAsync( array.Array, 0, idx, token );
 521
 522         //await this.WriteFormatInfo( args, stream, token, false, array );
 523      }
 524
 525      private FormatCodeInfo GetFormatCodes( Boolean isForWriting )
 526      {
 527         //formatCodesNumber = 0;
 528         //return null;
 529         DataFormat[] formatCodes;
 530         Int32 formatCodesNumber;
 531         // Prepare format information
 532         if (
 533            ( isForWriting && this._disableBinarySend )
 534            || ( !isForWriting && this._disableBinaryReceive )
 535            || this._types.All( t => !( isForWriting ? t.Functionality.SupportsWritingBinaryFormat : t.Functionality.Sup
 536         {
 537            // All parameters use text format
 538            formatCodesNumber = 0;
 539            formatCodes = null;
 540         }
 541         else if ( this._types.All( t => isForWriting ? t.Functionality.SupportsWritingBinaryFormat : t.Functionality.Su
 542         {
 543            // All parameters use binary format
 544            formatCodesNumber = 1;
 545            formatCodes = new[] { DataFormat.Binary };
 546         }
 547         else
 548         {
 549            // Each parameter will use the most optimal format
 550            formatCodesNumber = this._types.Length;
 551            formatCodes = new DataFormat[formatCodesNumber];
 552            for ( var i = 0; i < formatCodes.Length; ++i )
 553            {
 554               var thisType = this._types[i];
 555               formatCodes[i] = ( isForWriting ? thisType.Functionality.SupportsWritingBinaryFormat : thisType.Functiona
 556            }
 557         }
 558         return (formatCodes, formatCodesNumber);
 559      }
 560
 561      private async Task<DataFormat[]> WriteFormatInfo(
 562         BackendABIHelper args,
 563         Stream stream,
 564         CancellationToken token,
 565         Boolean isForWriting,
 566         ResizableArray<Byte> buffer
 567         )
 568      {
 569         var formatCodesTuple = this._sendCodes; // isForWriting ? this._sendCodes : this._receiveCodes;
 570         var formatCodes = formatCodesTuple.Item1;
 571
 572         buffer.CurrentMaxCapacity = sizeof( Int16 ) + sizeof( Int16 ) * ( formatCodes?.Length ?? 0 );
 573         var idx = 0;
 574         var array = buffer.Array;
 575         array.WritePgInt16( ref idx, formatCodesTuple.Item2 );
 576
 577         if ( formatCodes != null )
 578         {
 579            // Format codes, if necessary
 580            for ( var i = 0; i < formatCodes.Length; ++i )
 581            {
 582               array.WritePgInt16( ref idx, (Int16) formatCodes[i] );
 583            }
 584         }
 585
 586         await stream.WriteAsync( array, 0, idx, token );
 587
 588         return formatCodes;
 589      }
 590   }
 591
 592   internal sealed class DescribeMessage : FrontEndMessageWithSingleBody
 593   {
 594      internal static readonly DescribeMessage UNNAMED_STATEMENT = new DescribeMessage( true );
 595      internal static readonly DescribeMessage UNNAMED_PORTAL = new DescribeMessage( false );
 596
 597      private readonly Boolean _isStatement;
 598      private readonly String _name;
 599
 600      internal DescribeMessage( Boolean isStatement, String name = null )
 601         : base( FrontEndMessageCode.Describe )
 602      {
 603         this._isStatement = isStatement;
 604         this._name = name;
 605      }
 606
 607      protected override Int32 CalculateBufferSize( BackendABIHelper args, ResizableArray<Byte> array )
 608      {
 609         return 1 + args.GetStringSize( this._name, array );
 610      }
 611
 612      protected override void WriteMessageToBuffer( BackendABIHelper args, ResizableArray<Byte> array )
 613      {
 614         var idx = 0;
 615         array.Array
 616            .WriteByteToBytes( ref idx, (Byte) ( this._isStatement ? 'S' : 'P' ) )
 617            .WritePgString( ref idx, args.Encoding, this._name );
 618      }
 619   }
 620
 621   internal sealed class ExecuteMessage : FrontEndMessageWithSingleBody
 622   {
 623      internal static readonly ExecuteMessage UNNAMED_EXEC_ALL = new ExecuteMessage();
 624
 625      private readonly String _portalName;
 626      private readonly Int32 _maxRows;
 627
 628      internal ExecuteMessage( String portalName = null, Int32 maxRows = 0 )
 629         : base( FrontEndMessageCode.Execute )
 630      {
 631         this._portalName = portalName;
 632         this._maxRows = maxRows;
 633      }
 634
 635      protected override Int32 CalculateBufferSize( BackendABIHelper args, ResizableArray<Byte> array )
 636      {
 637         return args.GetStringSize( this._portalName, array ) + sizeof( Int32 );
 638      }
 639
 640      protected override void WriteMessageToBuffer( BackendABIHelper args, ResizableArray<Byte> array )
 641      {
 642         var idx = 0;
 643         array.Array
 644            .WritePgString( ref idx, args.Encoding, this._portalName )
 645            .WritePgInt32( ref idx, this._maxRows );
 646      }
 647   }
 648
 649   internal sealed class SSLRequestMessage : FrontEndMessage
 650   {
 651      internal static readonly SSLRequestMessage INSTANCE = new SSLRequestMessage();
 652
 653      private SSLRequestMessage()
 654      {
 655
 656      }
 657
 658      protected override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 659      {
 660         return sizeof( Int32 );
 661      }
 662
 663      protected override async Task DoSendMessageAsync(
 664         BackendABIHelper args,
 665         Stream stream,
 666         Int32 size,
 667         CancellationToken token,
 668         ResizableArray<Byte> array
 669         )
 670      {
 671         var idx = 0;
 672         // We don't have front end message code, so we need to write size ourselves.
 673         array.Array
 674            .WritePgInt32( ref idx, size )
 675            .WritePgInt32( ref idx, 80877103 );// As per spec
 676         await stream.WriteAsync( array.Array, 0, idx, token );
 677      }
 678   }
 679
 680   internal sealed class CopyDataFrontEndMessage : FrontEndMessageWithCode
 681   {
 682      private readonly Byte[] _data;
 683      private readonly Int32 _offset;
 684      private readonly Int32 _count;
 685
 686      public CopyDataFrontEndMessage( Byte[] data, Int32 offset, Int32 count )
 687         : base( FrontEndMessageCode.CopyData )
 688      {
 689         this._data = data;
 690         this._offset = offset;
 691         this._count = count;
 692      }
 693
 694      protected override Int32 CalculateSize( BackendABIHelper args, ResizableArray<Byte> array )
 695      {
 696         return this._count - this._offset;
 697      }
 698
 699      protected override async Task PerformSendAfterWriteAsync(
 700         BackendABIHelper args,
 701         Stream stream,
 702         Int32 size,
 703         CancellationToken token,
 704         ResizableArray<Byte> array
 705         )
 706      {
 707         await stream.WriteAsync( this._data, this._offset, this._count, token );
 708      }
 709
 710   }
 711
 712   internal enum FrontEndMessageCode : byte
 713   {
 714      Bind = (Byte) 'B',
 715      Close = (Byte) 'C',
 716      Describe = (Byte) 'D',
 717      Execute = (Byte) 'E',
 718      //FunctionCall = (Byte) 'F', // Deprecated
 719      Flush = (Byte) 'H',
 720      Parse = (Byte) 'P',
 721      Query = (Byte) 'Q',
 722      Sync = (Byte) 'S',
 723      Termination = (Byte) 'X',
 724      PasswordMessage = (Byte) 'p',
 725      CopyDone = (Byte) 'c',
 726      CopyData = (Byte) 'd',
 727   }
 728
 729   internal static partial class CBAMExtensions
 730   {
 731      public static Byte[] WritePgInt16( this Byte[] array, ref Int32 idx, Int32 value )
 732      {
 733         array.WriteInt16BEToBytes( ref idx, (Int16) value );
 734         return array;
 735      }
 736
 737      public static Byte[] WritePgString( this Byte[] array, ref Int32 idx, IEncodingInfo encoding, String value )
 738      {
 739         if ( !String.IsNullOrEmpty( value ) )
 740         {
 741            idx += encoding.Encoding.GetBytes( value, 0, value.Length, array, idx );
 742         }
 743         array[idx++] = 0;
 744         return array;
 745      }
 746   }
 747}
 748
 749public static partial class E_CBAM
 750{
 751
 752
 753   internal static Int32 GetStringSize( this BackendABIHelper args, String str, ResizableArray<Byte> array )
 754   {
 628755      var retVal = String.IsNullOrEmpty( str ) ? 1 : ( args.Encoding.Encoding.GetByteCount( str ) + 1 );
 633756      array.CurrentMaxCapacity = retVal;
 632757      return retVal;
 758   }
 759
 760   internal static async Task WriteString( this BackendABIHelper args, Stream stream, String str, CancellationToken toke
 761   {
 762      //if ( !String.IsNullOrEmpty( str ) )
 763      //{
 764      //   await args.WriteStringPart( stream, str, 0, str.Length );
 765      //}
 766      //args.Buffer.Array[0] = 0;
 767      //// Send terminating zero
 768      //await stream.WriteAsync( args.Buffer.Array, 0, 1 );
 485769      var idx = 0;
 486770      array.Array.WritePgString( ref idx, args.Encoding, str );
 486771      await stream.WriteAsync( array.Array, 0, idx, token );
 489772   }
 773
 774   //public static async Task WriteStringPart( this BackendABIHelper args, Stream stream, String str, Int32 stringStart,
 775   //{
 776   //   if ( !String.IsNullOrEmpty( str ) && stringLength > 0 )
 777   //   {
 778   //      var size = args.Encoding.Encoding.GetBytes( str, stringStart, stringLength, args.Buffer.Array, 0 );
 779   //      await stream.WriteAsync( args.Buffer.Array, 0, size );
 780   //   }
 781   //}
 782
 783
 784}

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/VendorSpecific.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 System;
 19using System.Collections.Generic;
 20using System.IO;
 21using System.Text;
 22using System.Threading.Tasks;
 23using UtilPack;
 24
 25using TReader = UtilPack.PeekablePotentiallyAsyncReader<System.Char?>;
 26
 27namespace CBAM.SQL.PostgreSQL.Implementation
 28{
 29   internal static class Parser
 30   {
 31      // Helper class to keep track of how many chars has been read from the underlying reader
 32      private sealed class TextReaderWrapper : TextReader
 33      {
 34         private readonly TextReader _reader;
 35         private Int32 _charsRead;
 36
 37         internal TextReaderWrapper( TextReader reader )
 38         {
 39            ArgumentValidator.ValidateNotNull( "Reader", reader );
 40
 41            this._reader = reader;
 42            this._charsRead = 0;
 43         }
 44
 45         public Int32 CharsRead
 46         {
 47            get
 48            {
 49               return this._charsRead;
 50            }
 51         }
 52
 53         public override Int32 Read()
 54         {
 55            var retVal = this._reader.Read();
 56            if ( retVal != -1 )
 57            {
 58               ++this._charsRead;
 59            }
 60            return retVal;
 61         }
 62
 63         public override Int32 Peek()
 64         {
 65            return this._reader.Peek();
 66         }
 67
 68         protected override void Dispose( bool disposing )
 69         {
 70            // Do nothing - we don't want to close underlying reader.
 71         }
 72      }
 73
 74      // Returns amount of characters read
 75      internal static async ValueTask<Int32[]> ParseStringForNextSQLStatement(
 76         TReader reader,
 77         Boolean standardConformingStrings,
 78         Func<Int32> onParameter
 79         )
 80      {
 81         var parenthesisLevel = 0;
 82         List<Int32> paramIndicesList = null;
 83         var queryEndEncountered = false;
 84         Char? prev1 = null, prev2 = null;
 85         Char? c;
 86
 87         while ( !queryEndEncountered && ( c = await reader.TryReadNextAsync() ).HasValue )
 88         {
 89            switch ( c )
 90            {
 91               case '\'':
 92                  await ParseSingleQuotes( reader, standardConformingStrings, prev1, prev2 );
 93                  break;
 94               case '"':
 95                  await ParseDoubleQuotes( reader );
 96                  break;
 97               case '-':
 98                  await ParseLineComment( reader );
 99                  break;
 100               case '/':
 101                  await ParseBlockComment( reader );
 102                  break;
 103               case '$':
 104                  await ParseDollarQuotes( reader, prev1 );
 105                  break;
 106               case '(':
 107                  ++parenthesisLevel;
 108                  break;
 109               case ')':
 110                  --parenthesisLevel;
 111                  break;
 112               case '?':
 113                  if ( onParameter != null )
 114                  {
 115                     if ( paramIndicesList == null )
 116                     {
 117                        paramIndicesList = new List<Int32>();
 118                     }
 119                     paramIndicesList.Add( onParameter() );
 120                  }
 121                  break;
 122               case ';':
 123                  if ( parenthesisLevel == 0 )
 124                  {
 125                     queryEndEncountered = true;
 126                  }
 127                  break;
 128            }
 129            prev2 = prev1;
 130            prev1 = c;
 131
 132         }
 133
 134         return paramIndicesList == null ? null : paramIndicesList.ToArray();
 135
 136      }
 137
 138
 139      // See http://www.postgresql.org/docs/9.1/static/sql-syntax-lexical.html for String Constants with C-style Escapes
 140      // Returns index of the single quote character ending this single quote sequence
 141      internal static async ValueTask<Boolean> ParseSingleQuotes(
 142         TReader reader,
 143         Boolean standardConformingStrings,
 144         Char? prev1,
 145         Char? prev2
 146         )
 147      {
 148         Char? c;
 149         if ( !standardConformingStrings
 150            && prev1.HasValue
 151            && prev2.HasValue
 152            && ( prev1 == 'e' || prev1 == 'E' )
 153            && CharTerminatesIdentifier( prev2.Value )
 154            )
 155         {
 156            // C-Style escaping
 157            // Treat backslashes as escape character
 158            Char prev = '\0';
 159            while ( ( c = await reader.TryReadNextAsync() ).HasValue )
 160            {
 161               if ( c != '\\' && prev != '\\' && await CheckSingleQuote( reader, c.Value ) )
 162               {
 163                  break;
 164               }
 165               prev = c.Value;
 166            }
 167         }
 168         else
 169         {
 170            // Don't treat backslashes as escape character
 171            while ( ( c = await reader.TryReadNextAsync() ).HasValue && !await CheckSingleQuote( reader, c.Value ) ) ;
 172         }
 173
 174         return true;
 175      }
 176
 177      internal static async ValueTask<Boolean> ParseDoubleQuotes(
 178         TReader reader
 179         )
 180      {
 181         Char? c;
 182         while ( ( c = await reader.TryReadNextAsync() ).HasValue )
 183         {
 184            if ( c == '"' )
 185            {
 186               // Check for double-doublequote
 187               if ( ( await reader.TryPeekAsync() ).IsOfValue( '"' ) )
 188               {
 189                  await reader.ReadNextAsync();
 190               }
 191               else
 192               {
 193                  break;
 194               }
 195            }
 196         }
 197
 198         return true;
 199      }
 200
 201      internal static async ValueTask<Boolean> ParseLineComment(
 202         TReader reader
 203         )
 204      {
 205         if ( ( await reader.TryPeekAsync() ).IsOfValue( '-' ) )
 206         {
 207            // Line comment starting
 208            Char? c;
 209            while ( ( c = await reader.TryReadNextAsync() ).HasValue && c != '\r' && c != '\n' ) ;
 210         }
 211         return true;
 212      }
 213
 214
 215      internal static async ValueTask<Boolean> ParseBlockComment(
 216         TReader reader
 217         )
 218      {
 219         if ( ( await reader.TryPeekAsync() ).IsOfValue( '*' ) )
 220         {
 221            // Block comment starting
 222            // SQL spec says block comments nest
 223            var level = 1;
 224            await reader.ReadNextAsync();
 225            Char? prev = null;
 226            Char? cur = null;
 227            var levelChanged = false;
 228            while ( level != 0 && ( cur = await reader.ReadNextAsync() ).HasValue )
 229            {
 230               var oldLevel = level;
 231               if ( !levelChanged ) // Don't process '*/*' or '/*/' twice
 232               {
 233                  if ( prev.HasValue )
 234                  {
 235                     if ( prev == '*' && cur == '/' )
 236                     {
 237                        // Block comment ending
 238                        --level;
 239                     }
 240                     else if ( prev == '/' && cur == '*' )
 241                     {
 242                        // Nested block comment
 243                        ++level;
 244                     }
 245                  }
 246               }
 247
 248               levelChanged = level != oldLevel;
 249               prev = cur;
 250            }
 251         }
 252
 253         return true;
 254      }
 255
 256      // See http://www.postgresql.org/docs/9.1/static/sql-syntax-lexical.html for dollar quote spec
 257      internal static async ValueTask<Boolean> ParseDollarQuotes(
 258         TReader reader,
 259         Char? prev
 260         )
 261      {
 262         var c = await reader.TryPeekAsync();
 263         if ( c.HasValue && ( !prev.HasValue || !IsIdentifierContinuationCharacter( prev.Value ) ) )
 264         {
 265            Char[] tag = null;
 266            if ( c == '$' )
 267            {
 268               tag = Empty<Char>.Array;
 269            }
 270            else if ( IsDollarQuoteTagStartCharacter( c.Value ) )
 271            {
 272               var list = new List<Char>();
 273               while ( ( c = await reader.TryPeekAsync() ).HasValue )
 274               {
 275                  if ( c == '$' )
 276                  {
 277                     tag = list.ToArray();
 278                     break;
 279                  }
 280                  else if ( !IsDollarQuoteTagContinuationCharacter( c.Value ) )
 281                  {
 282                     break;
 283                  }
 284                  else
 285                  {
 286                     list.Add( await reader.ReadNextAsync() );
 287                  }
 288               }
 289            }
 290
 291            if ( tag != null )
 292            {
 293               // Read the tag-ending dollar sign
 294               await reader.ReadNextAsync();
 295               var tagLen = tag.Length;
 296
 297               var isEmptyTag = tagLen == 0;
 298               var array = isEmptyTag ? null : new Char[tagLen];
 299               var arrayIdx = tagLen - 1;
 300               while ( ( c = await reader.TryReadNextAsync() ).HasValue )
 301               {
 302                  if ( c == '$' )
 303                  {
 304                     // Check if this is double-dollar-sign for empty tag, or that previous characters are same as tag
 305                     if ( isEmptyTag && prev == '$' )
 306                     {
 307                        break;
 308                     }
 309                     else if ( !isEmptyTag && CheckForCircularlyFilledArray( tag, tagLen, array, arrayIdx ) )
 310                     {
 311                        break;
 312                     }
 313                  }
 314
 315                  if ( !isEmptyTag )
 316                  {
 317                     if ( tag.Length > 1 )
 318                     {
 319                        if ( arrayIdx == tag.Length - 1 )
 320                        {
 321                           arrayIdx = 0;
 322                        }
 323                        else
 324                        {
 325                           ++arrayIdx;
 326                        }
 327                     }
 328                     array[arrayIdx] = (Char) c;
 329                  }
 330
 331                  prev = c;
 332               }
 333            }
 334         }
 335
 336         return true;
 337      }
 338
 339      // Returns true if this character ends string literal
 340      private static async ValueTask<Boolean> CheckSingleQuote(
 341         TReader reader,
 342         Char prevChar
 343         )
 344      {
 345         var retVal = prevChar == '\'';
 346         if ( retVal )
 347         {
 348            Char? peek;
 349            if ( ( peek = await reader.TryPeekAsync() ).HasValue )
 350            {
 351               // Check for double quotes
 352               if ( peek == '\'' )
 353               {
 354                  await reader.ReadNextAsync();
 355                  retVal = false;
 356               }
 357               else if ( peek == '\n' || peek == '\r' )
 358               {
 359                  // Check for newline-separated string literal ( http://www.postgresql.org/docs/9.1/static/sql-syntax-l
 360                  while ( peek.HasValue && peek == '\n' || peek == '\r' )
 361                  {
 362                     peek = await reader.ReadNextAsync();
 363                  }
 364
 365                  if ( peek.HasValue && peek == '\'' )
 366                  {
 367                     retVal = false;
 368                  }
 369               }
 370            }
 371         }
 372
 373         return retVal;
 374      }
 375
 376      // Returns true if character terminates identifier in backend parser
 377      private static Boolean CharTerminatesIdentifier( Char c )
 378      {
 379         return c == '"' || IsSpace( c ) || IsOperatorChar( c );
 380      }
 381
 382      // The functions below must be kept in sync with logic of pgsql/src/backend/parser/scan.l
 383
 384      // Returns true if character is treated as space character in backend parser
 385      internal static Boolean IsSpace( Char c )
 386      {
 387         return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
 388      }
 389
 390      // Returns true if the given character is a valid character for an operator in backend parser
 391      private static Boolean IsOperatorChar( Char c )
 392      {
 393         /*
 394          * Extracted from operators defined by {self} and {op_chars}
 395          * in pgsql/src/backend/parser/scan.l.
 396          */
 397         return ",()[].;:+-*/%^<>=~!@#&|`?".IndexOf( c ) != -1;
 398      }
 399
 400      // Checks wehether character is valid as second or later character of an identifier
 401      private static Boolean IsIdentifierContinuationCharacter( Char c )
 402      {
 403         return ( c >= 'a' && c <= 'z' )
 404            || ( c >= 'A' && c <= 'Z' )
 405            || c == '_'
 406            || c > 127
 407            || ( c >= '0' && c <= '9' )
 408            || c == '$';
 409      }
 410
 411      // Checks wthether character is valid as first character of dollar quote tag
 412      private static Boolean IsDollarQuoteTagStartCharacter( Char c )
 413      {
 414         return ( c >= 'a' && c <= 'z' )
 415            || ( c >= 'A' && c <= 'Z' )
 416            || c == '_'
 417            || c > 127;
 418      }
 419
 420      // Checks whether character is valid as second or later character of dollar quote tag
 421      private static Boolean IsDollarQuoteTagContinuationCharacter( Char c )
 422      {
 423         return ( c >= 'a' && c <= 'z' )
 424            || ( c >= 'A' && c <= 'Z' )
 425            || c == '_'
 426            || ( c >= '0' && c <= '9' )
 427            || c > 127;
 428
 429      }
 430
 431      // auxArrayIndex = index of last set character in auxArray
 432      internal static Boolean CheckForCircularlyFilledArray( Char[] referenceDataArray, Int32 refLen, Char[] auxArray, I
 433      {
 434         var min = auxArrayIndex + 1 - refLen;
 435         var i = refLen - 1;
 436         if ( min >= 0 )
 437         {
 438            // Enough to check that last auxLen chars are same (do check backwards)
 439            for ( var j = auxArrayIndex; i >= 0; --i, --j )
 440            {
 441               if ( referenceDataArray[i] != auxArray[j] )
 442               {
 443                  return false;
 444               }
 445            }
 446         }
 447         else
 448         {
 449            var j = auxArrayIndex;
 450            for ( ; j >= 0; --j, --i )
 451            {
 452               if ( referenceDataArray[i] != auxArray[j] )
 453               {
 454                  return false;
 455               }
 456            }
 457
 458            for ( j = auxArray.Length - 1; i >= 0; --i, --j )
 459            {
 460               if ( referenceDataArray[i] != auxArray[j] )
 461               {
 462                  return false;
 463               }
 464            }
 465         }
 466
 467         return true;
 468      }
 469   }
 470}
 471
 472public static partial class E_CBAM
 473{
 474   internal static Boolean IsOfValue( this Char? nullable, Char value )
 475   {
 0476      return nullable.HasValue && nullable.Value == value;
 477   }
 478}