Summary

Class:CBAM.SQL.PostgreSQL.Implementation.PgSQLTypeFunctionalityForArrays
Assembly:CBAM.SQL.PostgreSQL.Implementation
File(s):/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Types.Array.cs
Covered lines:406
Uncovered lines:50
Coverable lines:456
Total lines:937
Line coverage:89%
Branch coverage:83.9%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.ctor(...)401%1%
ChangeTypeFrameworkToPgSQL(...)401%0.5%
EqualsIgnoreNullability(...)201%0.5%
GetActualIfNullable(...)200%0%
ChangeTypePgSQLToFramework(...)100%0%
GetBackendSize(...)400.75%0.75%
GetBackendBinarySize(...)601%1%
GetBackendTextSize(...)1001%1%
ReadBackendValueAsync()500.727%1%
WriteBackendValueAsync()600.857%1%
ReadArrayText()2601%1%
ReadArrayTextHeader()1600.971%1%
ReadArrayTextDimensionEnd()1501%1%
MoveNextMultiDimensionalIndex(...)1001%1%
ReadArrayElementText()3600.982%1%
IsNullArrayElement(...)1601%0.75%
ReadArrayBinary()3000%0%
WriteArrayText()3001%1%
WriteArrayBinary()1501%1%

File(s)

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Protocol.Types.Array.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 System;
 20using System.Collections.Generic;
 21using System.IO;
 22using System.Linq;
 23using System.Reflection;
 24using System.Text;
 25using System.Threading;
 26using System.Threading.Tasks;
 27using UtilPack;
 28
 29
 30namespace CBAM.SQL.PostgreSQL.Implementation
 31{
 32   using TextSizeAdditionalInfo = ValueTuple<BackendSizeInfo[], ValueTuple<Int32[], Int32[], Int32>>;
 33
 34   internal sealed class PgSQLTypeFunctionalityForArrays : PgSQLTypeFunctionality
 35   {
 36      internal const Byte ARRAY_START = (Byte) '{';
 37      internal const Byte ARRAY_END = (Byte) '}';
 38      internal const Char ESCAPE = '\\';
 39      internal const Char QUOTE = '"';
 40      internal const Byte ARRAY_LOBO_START = (Byte) '[';
 41      internal const Byte ARRAY_LOBO_END = (Byte) ']';
 42      internal const Byte ARRAY_LOBO_SPEC_SEPARATOR = (Byte) '=';
 43      internal const Char DIM_SEPARATOR = ':';
 44      internal const Int32 NULL_CHAR_COUNT = 4;
 45      internal const String NULL_STRING = "NULL";
 46
 47      private readonly Array _emptyArray;
 48      private readonly Type _arrayElementType;
 49      private readonly Lazy<TypeFunctionalityInformation> _elementTypeInfo;
 50
 53451      public PgSQLTypeFunctionalityForArrays(
 53452         TypeRegistry protocol,
 53453         ref Type arrayElementType,
 53454         Int32 elementTypeID
 53455         )
 56      {
 54457         this._elementTypeInfo = new Lazy<TypeFunctionalityInformation>( () => protocol.TryGetTypeInfo( elementTypeID ),
 53458         if ( arrayElementType.GetTypeInfo().IsValueType && !arrayElementType.IsNullable() )
 59         {
 60            // Allow nulls
 40861            arrayElementType = typeof( Nullable<> ).MakeGenericType( arrayElementType );
 62         }
 63
 64         // TODO maybe make CLRType getter throw exception? Since the actual type may be X[], X[,], X[,,], etc...?
 53465         this._arrayElementType = arrayElementType;
 53466         this._emptyArray = Array.CreateInstance( arrayElementType, 0 );
 53467      }
 68
 069      public Boolean SupportsReadingBinaryFormat => this._elementTypeInfo.Value.Functionality.SupportsReadingBinaryForma
 70
 4571      public Boolean SupportsWritingBinaryFormat => this._elementTypeInfo.Value.Functionality.SupportsWritingBinaryForma
 72
 73      public Object ChangeTypeFrameworkToPgSQL( PgSQLTypeDatabaseData dbData, Object obj )
 74      {
 75         // We will enter here for multidimensional arrays, from BindMessage
 1176         var objType = obj.GetType();
 1277         return objType.IsArray
 1278            && EqualsIgnoreNullability( objType.GetElementType(), this._arrayElementType ) ? // The WriteArrayText and W
 1279               obj :
 1280               throw new InvalidCastException( $"The object to cast must be single- or multidimensionsal array with elem
 81      }
 82
 83      private static Boolean EqualsIgnoreNullability( Type x, Type y )
 84      {
 1285         return Equals( x, y )
 1286            || Equals( GetActualIfNullable( x ), GetActualIfNullable( y ) );
 87      }
 88
 89      private static Type GetActualIfNullable( Type type )
 90      {
 091         return type.IsNullable( out var actual ) ? actual : type;
 92      }
 93
 94      public Object ChangeTypePgSQLToFramework( PgSQLTypeDatabaseData dbData, Object obj, Type typeTo )
 95      {
 96         // TODO cast all elements of array...?
 097         throw new InvalidCastException();
 98      }
 99
 100      public BackendSizeInfo GetBackendSize( DataFormat dataFormat, PgSQLTypeDatabaseData boundData, BackendABIHelper he
 101      {
 32102         switch ( dataFormat )
 103         {
 104            case DataFormat.Text:
 8105               return this.GetBackendTextSize( boundData, helper, value );
 106            case DataFormat.Binary:
 23107               return this.GetBackendBinarySize( boundData, helper, value );
 108            default:
 0109               throw new NotSupportedException( $"Data format {dataFormat} is not recognized." );
 110         }
 111      }
 112
 113      private BackendSizeInfo GetBackendBinarySize( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, Object val
 114      {
 22115         var array = (Array) value;
 116         BackendSizeInfo retVal;
 23117         var arrayLength = array.Length;
 118         // The header size is three integers (rank, null map, element type id), and then two integers for each rank
 23119         var size = sizeof( Int32 ) * 3;
 120         BackendSizeInfo[] elementSizes;
 24121         if ( arrayLength > 0 )
 122         {
 21123            size += array.Rank * 2 * sizeof( Int32 );
 21124            elementSizes = new BackendSizeInfo[arrayLength];
 18125            var i = 0;
 18126            var elementInfo = this._elementTypeInfo.Value;
 179127            foreach ( var elem in array )
 128            {
 71129               var sizeInfo = elementInfo.Functionality.GetBackendSizeCheckNull( DataFormat.Binary, elementInfo.Database
 70130               elementSizes[i++] = sizeInfo;
 70131               size += sizeof( Int32 );
 71132               if ( sizeInfo.ByteCount > 0 )
 133               {
 62134                  size += sizeInfo.ByteCount;
 135               }
 136            }
 137         }
 138         else
 139         {
 3140            elementSizes = null;
 141         }
 20142         retVal = new BackendSizeInfo( size, elementSizes );
 143
 22144         return retVal;
 145      }
 146
 147      private BackendSizeInfo GetBackendTextSize( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, Object value
 148      {
 8149         var array = (Array) value;
 8150         var helperEncoding = helper.Encoding;
 8151         var encoding = helperEncoding.Encoding;
 8152         var asciiSize = helperEncoding.BytesPerASCIICharacter;
 8153         var length = array.Length;
 154         BackendSizeInfo retVal;
 8155         if ( length <= 0 )
 156         {
 1157            retVal = new BackendSizeInfo( 2 * asciiSize );
 1158         }
 159         else
 160         {
 7161            var rank = array.Rank;
 7162            var bracesCount = 0; // Amount of array start/end braces
 7163            var innermostLength = array.GetLength( rank - 1 );
 7164            var delimitersCount = Math.Max( innermostLength - 1, 0 ); // Amount of delimiter characters
 165                                                                      // Iterate from second-innermost dimension towards
 22166            for ( var i = rank - 2; i >= 0; --i )
 167            {
 4168               var curLen = array.GetLength( i );
 4169               bracesCount = bracesCount * curLen + 2 * curLen;
 4170               delimitersCount = curLen - 1 + delimitersCount * curLen;
 171            }
 172
 173            // Remember outermost braces
 7174            bracesCount += 2;
 7175            var elementSizes = new BackendSizeInfo[length];
 7176            var elementInfo = this._elementTypeInfo.Value;
 7177            var j = 0;
 62178            foreach ( var elem in array )
 179            {
 24180               elementSizes[j++] = elementInfo.Functionality.GetBackendSizeCheckNull( DataFormat.Text, elementInfo.Datab
 181            }
 182
 183            // All the space taken by array structure information
 7184            var sizeForArrayInfra = encoding.GetByteCount( boundData.ArrayDelimiter ) * delimitersCount + bracesCount * 
 7185            var lobos = array.GetLowerBounds();
 7186            var loboSpecByteCount = 0;
 7187            Int32[] upbos = null;
 7188            if ( lobos != null )
 189            {
 190               // Bounds specification: "[lobo1:upbo1][lobo2:upbo2]...="
 2191               loboSpecByteCount = rank * 3 * asciiSize + asciiSize; // Amount of '[', ']', ':', and '='
 2192               upbos = new Int32[rank];
 12193               for ( var i = 0; i < rank; ++i )
 194               {
 195                  // Remember that (Pg)SQL array indexing starts from 1 by default
 4196                  upbos[i] = array.GetUpperBound( i ) + 1;
 4197                  ++lobos[i];
 198                  // Increment spec count
 4199                  loboSpecByteCount += helperEncoding.GetTextualIntegerRepresentationSize( lobos[i] ) + helperEncoding.G
 200               }
 2201               sizeForArrayInfra += loboSpecByteCount;
 202            }
 203
 7204            var nullSize = NULL_CHAR_COUNT * asciiSize;
 7205            retVal = new BackendSizeInfo(
 31206               sizeForArrayInfra + elementSizes.Aggregate( 0, ( cur, item ) => cur + ( item.ByteCount >= 0 ? item.ByteCo
 7207               (elementSizes, (lobos, upbos, loboSpecByteCount))
 7208               );
 209         }
 210
 8211         return retVal;
 212      }
 213
 214      public async ValueTask<Object> ReadBackendValueAsync(
 215         DataFormat dataFormat,
 216         PgSQLTypeDatabaseData boundData,
 217         BackendABIHelper helper,
 218         StreamReaderWithResizableBufferAndLimitedSize stream
 219         )
 220      {
 221         Array retVal;
 39222         switch ( dataFormat )
 223         {
 224            case DataFormat.Text:
 40225               if ( stream.TotalByteCount > 2 * helper.Encoding.BytesPerASCIICharacter )
 226               {
 35227                  retVal = await this.ReadArrayText( boundData, helper, stream );
 34228               }
 229               else
 230               {
 231                  // Empty array
 5232                  retVal = this._emptyArray;
 233               }
 5234               break;
 235            case DataFormat.Binary:
 0236               retVal = await this.ReadArrayBinary( boundData, helper, stream );
 0237               break;
 238            default:
 0239               throw new NotSupportedException( $"Data format {dataFormat} is not recognized." );
 240         }
 241
 40242         return retVal;
 40243      }
 244
 245      public async Task WriteBackendValueAsync(
 246         DataFormat dataFormat,
 247         PgSQLTypeDatabaseData boundData,
 248         BackendABIHelper helper,
 249         StreamWriterWithResizableBufferAndLimitedSize stream,
 250         Object value,
 251         BackendSizeInfo sizeInfo,
 252         Boolean isArrayElement
 253         )
 254      {
 29255         switch ( dataFormat )
 256         {
 257            case DataFormat.Text:
 8258               await this.WriteArrayText( boundData, helper, stream, (Array) value, sizeInfo );
 8259               break;
 260            case DataFormat.Binary:
 24261               await this.WriteArrayBinary( boundData, helper, stream, (Array) value, sizeInfo );
 22262               break;
 263            default:
 0264               throw new NotSupportedException( $"Data format {dataFormat} is not recognized." );
 265         }
 29266      }
 267
 268      private async Task<Array> ReadArrayText(
 269         PgSQLTypeDatabaseData boundData,
 270         BackendABIHelper helper,
 271         StreamReaderWithResizableBufferAndLimitedSize stream
 272         )
 273      {
 34274         (var rank, var lobos, var lengths, var retVal) = await this.ReadArrayTextHeader( boundData, helper, stream );
 275
 276         // Use exponentially expanding array instead of list. That way we can use Array.Copy right away when creating r
 35277         var useTempArray = retVal == null;
 35278         var totalCount = 0;
 279         // We only need temporary array if we are creating array without dimension specification prefix
 280         Int32[] retValIndices;
 281         ResizableArray<Object> tempArray;
 282         Int32 arrayDelimiterByteCount;
 35283         if ( useTempArray )
 284         {
 25285            tempArray = new ResizableArray<Object>( initialSize: 2, exponentialResize: true );
 23286            lengths = new Int32[rank];
 25287            retValIndices = null;
 24288            arrayDelimiterByteCount = -1;
 25289         }
 290         else
 291         {
 10292            retValIndices = new Int32[rank];
 10293            Array.Copy( lobos, retValIndices, rank );
 10294            tempArray = null;
 10295            arrayDelimiterByteCount = helper.Encoding.Encoding.GetByteCount( boundData.ArrayDelimiter );
 296         }
 297
 298         // We start with innermost array
 34299         var innermostArrayIndex = rank - 1;
 34300         var lowestEncounteredArrayEnd = innermostArrayIndex;
 34301         var asciiSize = helper.Encoding.BytesPerASCIICharacter;
 35302         Boolean hasMore = true;
 155303         while ( hasMore )
 304         {
 120305            (var value, var ending) = await this.ReadArrayElementText( boundData, helper, stream );
 306
 119307            if ( useTempArray )
 308            {
 78309               tempArray.CurrentMaxCapacity = totalCount + 1;
 80310               tempArray.Array[totalCount++] = value;
 80311               if ( lowestEncounteredArrayEnd == innermostArrayIndex )
 312               {
 30313                  ++lengths[innermostArrayIndex];
 314               }
 315
 316               // If array end encountered, we must find start of next element, if possible
 80317               if ( ending == ElementEndingWay.ArrayEnd )
 318               {
 50319                  (lowestEncounteredArrayEnd, lengths, hasMore) = await this.ReadArrayTextDimensionEnd(
 50320                     boundData,
 50321                     helper,
 50322                     stream,
 50323                     lowestEncounteredArrayEnd,
 50324                     lengths,
 50325                     rank
 50326                     );
 327               }
 48328            }
 329            else
 330            {
 40331               retVal.SetValue( value, retValIndices );
 40332               var dimsEnded = MoveNextMultiDimensionalIndex( lengths, retValIndices, lobos );
 333               // At this point, the ReadArrayElementText method has already read either complete array delimiter, or on
 334               // So we only need to skip thru bytes if we have read array end character (dimsEnded > 0)
 40335               if ( dimsEnded > 0 )
 336               {
 15337                  hasMore = dimsEnded < rank;
 15338                  if ( hasMore )
 339                  {
 340                     // Skip thru array end, array delimiter, and array start characters
 5341                     await stream.ReadMoreOrThrow( ( dimsEnded * 2 - 1 ) * asciiSize + arrayDelimiterByteCount );
 342                  }
 343               }
 344            }
 345
 119346            stream.EraseReadBytesFromBuffer();
 347         }
 348
 349         // Now, construct the actual array to return, if needed
 350         // If array to be returned is null at this stage, this means that no lower bound specifications were given.
 34351         if ( retVal == null )
 352         {
 25353            if ( rank == 1 )
 354            {
 355               // Create normal one-dimensional array (we always need to create it, since we must return X[] instead of 
 20356               retVal = Array.CreateInstance( this._arrayElementType, totalCount );
 357               // Populate it
 19358               Array.Copy( tempArray.Array, retVal, totalCount );
 20359            }
 360            else
 361            {
 362               // Create multi-dimensional array
 5363               retVal = Array.CreateInstance( this._arrayElementType, lengths );
 364               // Populate it
 5365               var curIndices = new Int32[lengths.Length];
 5366               var idx = 0;
 5367               var actualArray = tempArray.Array;
 368               do
 369               {
 60370                  var elem = actualArray[idx++];
 60371                  if ( elem != null )
 372                  {
 60373                     retVal.SetValue( elem, curIndices );
 374                  }
 60375                  MoveNextMultiDimensionalIndex( lengths, curIndices );
 60376               } while ( idx < totalCount );
 377            }
 378         }
 379
 34380         return retVal;
 381
 35382      }
 383
 384      private async ValueTask<(Int32 Rank, Int32[] Lobos, Int32[] Lengths, Array CreatedArray)> ReadArrayTextHeader(
 385         PgSQLTypeDatabaseData boundData,
 386         BackendABIHelper helper,
 387         StreamReaderWithResizableBufferAndLimitedSize stream
 388         )
 389      {
 34390         stream.EraseReadBytesFromBuffer();
 391
 392         Char curChar;
 393         // Read the optional "[lobo1:upbo1][lobo2:upbo2]...=" dimension specification into buffer.
 34394         var rank = 0;
 35395         var charReader = helper.CharacterReader;
 396         do
 397         {
 155398            curChar = await charReader.ReadNextAsync( stream );
 153399            if ( curChar == DIM_SEPARATOR )
 400            {
 20401               ++rank;
 402            }
 154403         } while ( curChar != ARRAY_START );
 404
 34405         Int32[] lobos = null;
 34406         Int32[] lengths = null;
 33407         Array retVal = null;
 34408         if ( rank > 0 )
 409         {
 410            // We encountered the explicit dimension specification. Backend should issue this only when there are 'speci
 411            // As a bonus, we will know the array dimensions before array elements start, and we can create the array to
 412            // instead of reading elements into temporary array
 10413            lobos = new Int32[rank];
 10414            lengths = new Int32[rank];
 10415            var encoding = helper.Encoding;
 10416            var asciiSize = encoding.BytesPerASCIICharacter;
 10417            var byteArray = stream.Buffer;
 10418            var idx = asciiSize; // Skip first '['
 60419            for ( var i = 0; i < rank; ++i )
 420            {
 421               // In (Pg)SQL, lower bounds normally start at 1. So 1 translates to 0 in CLR, 0 to -1, etc.
 20422               lobos[i] = encoding.ParseInt32Textual( byteArray, ref idx ) - 1;
 20423               idx += asciiSize; // Skip ':'
 20424               lengths[i] = encoding.ParseInt32Textual( byteArray, ref idx ) - lobos[i];
 20425               idx += asciiSize * 2; // Skip ']' and next '[' or '='
 426            }
 10427            retVal = Array.CreateInstance( this._arrayElementType, lengths, lobos );
 428         }
 429
 430         // Read amount of starting '{' characters. That will be the array rank (unless we already learned about the ran
 33431         rank = 0;
 432         Int32 prevIdx;
 433         do
 434         {
 54435            ++rank;
 53436            prevIdx = stream.ReadBytesCount;
 53437            curChar = await charReader.ReadNextAsync( stream );
 54438         } while ( curChar == ARRAY_START );
 439
 35440         if ( retVal != null && rank != retVal.Rank )
 441         {
 0442            throw new PgSQLException( "Backend array lower-bound specification had different rank than actual array spec
 443         }
 444
 445         // Back one character (the one we read, that wasn't array start character)
 35446         stream.UnreadBytes( stream.ReadBytesCount - prevIdx );
 447
 448         // Remember to get rid of array start characters currently in buffer (ReadArrayElementText expects clean buffer
 35449         stream.EraseReadBytesFromBuffer();
 450
 35451         return (rank, lobos, lengths, retVal);
 35452      }
 453
 454      private async ValueTask<(Int32 LowestEncounteredArrayEnd, Int32[] Lengths, Boolean HasMore)> ReadArrayTextDimensio
 455         PgSQLTypeDatabaseData boundData,
 456         BackendABIHelper helper,
 457         StreamReaderWithResizableBufferAndLimitedSize stream,
 458         Int32 lowestEncounteredArrayEnd,
 459         Int32[] lengths,
 460         Int32 rank
 461         )
 462      {
 50463         stream.EraseReadBytesFromBuffer();
 464
 49465         var wasArrayEnd = true;
 50466         var innermostArrayIndex = rank - 1;
 50467         var curArrayIndex = innermostArrayIndex;
 468         Char curChar;
 50469         var charReader = helper.CharacterReader;
 470
 90471         while ( wasArrayEnd && --curArrayIndex >= 0 )
 472         {
 473            // End current array block
 40474            lowestEncounteredArrayEnd = Math.Min( lowestEncounteredArrayEnd, curArrayIndex );
 40475            if ( curArrayIndex <= lowestEncounteredArrayEnd )
 476            {
 25477               lengths[curArrayIndex]++;
 478            }
 479
 480            // Read next character
 40481            curChar = await charReader.ReadNextAsync( stream );
 40482            wasArrayEnd = curChar == ARRAY_END;
 483         }
 484
 50485         var hasMore = curArrayIndex >= 0;
 50486         if ( hasMore )
 487         {
 488            // More arrays follow
 489            // Read until we are at innermost array level again
 55490            while ( curArrayIndex < innermostArrayIndex )
 491            {
 30492               curChar = await charReader.ReadNextAsync( stream );
 30493               if ( curChar == ARRAY_START )
 494               {
 30495                  ++curArrayIndex;
 496               }
 497            }
 498         }
 499
 50500         stream.EraseReadBytesFromBuffer();
 501
 50502         return (lowestEncounteredArrayEnd, lengths, hasMore);
 49503      }
 504
 505      private static Int32 MoveNextMultiDimensionalIndex(
 506         Int32[] lengths,
 507         Int32[] indices,
 508         Int32[] loBos = null
 509         )
 510      {
 116511         var i = indices.Length - 1;
 116512         if ( loBos == null )
 513         {
 180514            for ( ; i >= 0 && ++indices[i] == lengths[i]; --i )
 515            {
 52516               indices[i] = 0;
 517            }
 71518         }
 519         else
 520         {
 90521            for ( ; i >= 0 && ++indices[i] == lengths[i] + loBos[i]; --i )
 522            {
 25523               indices[i] = loBos[i];
 524            }
 525         }
 526
 116527         return lengths.Length - i - 1;
 528      }
 529
 530      // We never encounter empty arrays when calling this, since inner empty arrays are not possible, and whole empty a
 531      private async ValueTask<(Object Value, ElementEndingWay Ending)> ReadArrayElementText(
 532         PgSQLTypeDatabaseData boundData,
 533         BackendABIHelper helper,
 534         StreamReaderWithResizableBufferAndLimitedSize stream
 535         )
 536      {
 537         // Scan for element end
 118538         stream.EraseReadBytesFromBuffer();
 539
 120540         var insideQuote = false;
 120541         var delimLength = boundData.ArrayDelimiter.Length;
 119542         var delim0 = boundData.ArrayDelimiter[0];
 118543         var delim1 = delimLength > 1 ? boundData.ArrayDelimiter[1] : '\0';
 544         Int32 prevIdx;
 545         Char curChar;
 119546         Char curChar2 = '\0';
 119547         Boolean wasArrayDelimiter = false;
 119548         var prevWasEscape = false;
 119549         var asciiSize = helper.Encoding.BytesPerASCIICharacter;
 119550         var charReader = helper.CharacterReader;
 551         Boolean continueReading;
 552         do
 553         {
 369554            prevIdx = stream.ReadBytesCount;
 369555            var charNullable = await charReader.TryReadNextAsync( stream );
 369556            continueReading = charNullable.HasValue;
 369557            curChar = charNullable.GetValueOrDefault();
 369558            if ( continueReading )
 559            {
 370560               if ( prevWasEscape )
 561               {
 5562                  prevWasEscape = false;
 5563               }
 564               else
 565               {
 365566                  if ( delimLength > 1 && Char.IsHighSurrogate( curChar ) )
 567                  {
 0568                     curChar2 = await charReader.TryReadNextAsync( stream ) ?? '\0';
 569                  }
 570
 363571                  switch ( curChar )
 572                  {
 573                     case ESCAPE:
 574                     case QUOTE:
 24575                        if ( curChar == QUOTE )
 576                        {
 19577                           insideQuote = !insideQuote;
 19578                        }
 579                        else
 580                        {
 5581                           prevWasEscape = true;
 582                        }
 583                        // "Shift" the array
 24584                        stream.EraseReadBufferSegment( prevIdx, asciiSize );
 25585                        break;
 586                     default:
 338587                        wasArrayDelimiter = !insideQuote
 338588                           && (
 338589                              ( delimLength == 1 && curChar == delim0 )
 338590                              || ( delimLength > 1 && curChar == delim0 && curChar2 == delim1 )
 338591                              );
 340592                        continueReading = !wasArrayDelimiter && curChar != ARRAY_END;
 593                        break;
 594                  }
 595               }
 596            }
 370597         } while ( continueReading );
 598
 120599         var elementAndSeparatorSize = stream.ReadBytesCount;
 120600         var ending = wasArrayDelimiter ? ElementEndingWay.ArrayDelimiter : ( curChar == ARRAY_END ? ElementEndingWay.Ar
 601         Object arrayElement;
 120602         if ( ending == ElementEndingWay.Abnormal || IsNullArrayElement( helper.Encoding, stream.Buffer, prevIdx ) )
 603         {
 10604            arrayElement = null;
 10605         }
 606         else
 607         {
 110608            stream.UnreadBytes();
 110609            var elementTypeInfo = this._elementTypeInfo.Value;
 110610            using ( var elementStream = stream.CreateWithLimitedSizeAndSharedBuffer( prevIdx ) )
 611            {
 110612               arrayElement = await elementTypeInfo.Functionality.ReadBackendValueAsync(
 110613                        DataFormat.Text,
 110614                        elementTypeInfo.DatabaseData,
 110615                        helper,
 110616                        elementStream
 110617                        );
 618
 108619            }
 620
 621            // Re-read separator
 110622            await stream.ReadMoreOrThrow( elementAndSeparatorSize - prevIdx );
 623         }
 624
 625         // Erase all previous read data (element + separator)
 120626         stream.EraseReadBytesFromBuffer();
 120627         return (
 120628            arrayElement,
 120629            ending
 120630            );
 120631      }
 632
 633      private const Int32 CHUNK_SIZE = 1024;
 634
 635
 636
 637
 638      private enum ElementEndingWay
 639      {
 640         ArrayDelimiter,
 641         ArrayEnd,
 642         Abnormal
 643      }
 644
 645      private static Boolean IsNullArrayElement(
 646         IEncodingInfo encoding,
 647         Byte[] array,
 648         Int32 elementByteCount
 649         )
 650      {
 118651         Int32 idx = 0;
 652         Byte lastASCIIByte;
 118653         return elementByteCount == encoding.BytesPerASCIICharacter * NULL_CHAR_COUNT
 118654            // Poor man's case insensitive matching
 118655            && ( ( lastASCIIByte = encoding.ReadASCIIByte( array, ref idx ) ) == 'N' || lastASCIIByte == 'n' )
 118656            && ( ( lastASCIIByte = encoding.ReadASCIIByte( array, ref idx ) ) == 'U' || lastASCIIByte == 'u' )
 118657            && ( ( lastASCIIByte = encoding.ReadASCIIByte( array, ref idx ) ) == 'L' || lastASCIIByte == 'l' )
 118658            && ( ( lastASCIIByte = encoding.ReadASCIIByte( array, ref idx ) ) == 'L' || lastASCIIByte == 'l' )
 118659            ;
 660      }
 661
 662      private async Task<Array> ReadArrayBinary(
 663         PgSQLTypeDatabaseData boundData,
 664         BackendABIHelper helper,
 665         StreamReaderWithResizableBufferAndLimitedSize stream
 666         )
 667      {
 0668         await stream.ReadOrThrow( sizeof( Int32 ) );
 0669         var idx = 0;
 0670         var rank = stream.Buffer.ReadPgInt32( ref idx );
 671         Array retVal;
 0672         if ( rank < 0 )
 673         {
 0674            throw new PgSQLException( "Array rank must be zero or more." );
 675         }
 0676         else if ( rank == 0 )
 677         {
 678            // Empty array
 0679            retVal = this._emptyArray;
 0680         }
 681         else
 682         {
 683            // Read the rest of the header (null-map (Int32), element type id (Int32), and rank infos (2 integers per ra
 0684            await stream.ReadMoreOrThrow( ( 2 + 2 * rank ) * sizeof( Int32 ) );
 685            // Skip null-map and element type id.
 0686            idx = sizeof( Int32 ) * 3;
 687
 688            // Read lengths and lower bounds for dimensions
 0689            var lengths = new Int32[rank];
 0690            Int32[] loBos = null;
 0691            for ( var i = 0; i < rank; ++i )
 692            {
 0693               var curNumber = stream.Buffer.ReadPgInt32( ref idx );
 0694               lengths[i] = curNumber;
 0695               curNumber = stream.Buffer.ReadPgInt32( ref idx );
 0696               if ( curNumber != 1 ) // In SQL, default min lo bo is 1. In C#, the default is 0.
 697               {
 0698                  if ( loBos == null )
 699                  {
 0700                     loBos = new Int32[rank];
 701                  }
 0702                  loBos[i] = curNumber - 1;
 703               }
 704            }
 705
 706            // Create & populate array instance
 0707            stream.EraseReadBytesFromBuffer();
 0708            var elemInfo = this._elementTypeInfo.Value;
 0709            if ( rank == 1 && loBos == null )
 710            {
 711               // Can just use normal array
 0712               var len = lengths[0];
 0713               retVal = Array.CreateInstance( this._arrayElementType, len );
 0714               for ( var i = 0; i < len; ++i )
 715               {
 0716                  var curInfo = await elemInfo.Functionality.ReadBackendValueCheckNull( DataFormat.Binary, elemInfo.Data
 0717                  retVal.SetValue( curInfo.Value, i );
 718               }
 0719            }
 720            else
 721            {
 722               // Have to create multi-dimensional array
 0723               retVal = loBos == null ? Array.CreateInstance( this._arrayElementType, lengths ) : Array.CreateInstance( 
 0724               var indices = new Int32[rank];
 0725               if ( loBos != null )
 726               {
 0727                  Array.Copy( loBos, indices, rank );
 728               }
 729               do
 730               {
 0731                  var curInfo = await elemInfo.Functionality.ReadBackendValueCheckNull( DataFormat.Binary, elemInfo.Data
 0732                  retVal.SetValue( curInfo.Value, indices );
 0733               } while ( MoveNextMultiDimensionalIndex( lengths, indices, loBos ) < rank );
 0734            }
 0735         }
 736
 0737         return retVal;
 0738      }
 739
 740      private async Task WriteArrayText(
 741         PgSQLTypeDatabaseData boundData,
 742         BackendABIHelper helper,
 743         StreamWriterWithResizableBufferAndLimitedSize stream,
 744         Array value,
 745         BackendSizeInfo sizeInfo
 746         )
 747      {
 8748         var encoding = helper.Encoding;
 8749         var length = value.Length;
 8750         if ( length <= 0 )
 751         {
 752            // Just write '{}'
 2753            stream.AppendToBytes( 2 * encoding.BytesPerASCIICharacter, ( bArray, idx, count ) => encoding.WriteASCIIByte
 1754         }
 755         else
 756         {
 7757            var additionalSizeInfo = (TextSizeAdditionalInfo) sizeInfo.CustomInformation;
 7758            var loboInfo = additionalSizeInfo.Item2;
 7759            var rank = value.Rank;
 7760            var lobos = loboInfo.Item1;
 7761            var asciiSize = encoding.BytesPerASCIICharacter;
 7762            if ( lobos != null )
 763            {
 764               // We must write the lower bound specification: '[lobo1:upbo1][lobo2:upbo2]...='
 2765               var upbos = loboInfo.Item2;
 2766               stream.AppendToBytes( loboInfo.Item3, ( array, idx, count ) =>
 2767               {
 14768                  for ( var i = 0; i < rank; ++i )
 2769                  {
 2770                     // Both lobos and upbos have correct values from GetBackendBinarySize method
 6771                     encoding
 6772                            .WriteASCIIByte( array, ref idx, ARRAY_LOBO_START )
 6773                            .WriteIntegerTextual( array, ref idx, lobos[i] )
 6774                            .WriteASCIIByte( array, ref idx, (Byte) DIM_SEPARATOR )
 6775                            .WriteIntegerTextual( array, ref idx, upbos[i] )
 6776                            .WriteASCIIByte( array, ref idx, ARRAY_LOBO_END );
 2777                  }
 2778
 2779                  // Write final '='
 4780                  encoding.WriteASCIIByte( array, ref idx, ARRAY_LOBO_SPEC_SEPARATOR );
 4781               } );
 782
 783            }
 784
 7785            Int32[] indices = null;
 7786            Int32[] lengths = null;
 7787            if ( rank > 1 )
 788            {
 789               // We have to use indices and lengths in order to properly emit '}' and '{' in between array elements
 790               // N.B.! One-ranked array with lobo-specification still doesn't need to use indices and lengths, as it wo
 791               // We also don't need to initialize indices with lower bounds, as we only need to know when dimensions en
 2792               indices = new Int32[rank];
 2793               lengths = value.GetLengths();
 794            }
 795
 796            // Write '{'s
 7797            var rankASCIISize = rank * asciiSize;
 7798            stream.AppendToBytes( rankASCIISize, ( bArray, idx, count ) =>
 7799            {
 43800               for ( var i = 0; i < rank; ++i )
 7801               {
 18802                  encoding.WriteASCIIByte( bArray, ref idx, ARRAY_START );
 7803               }
 14804            } );
 805
 806            // Send prefix, then send elements
 7807            await stream.FlushAsync();
 808
 7809            var elementSizeInfos = additionalSizeInfo.Item1;
 7810            var eIdx = 0;
 7811            var elementTypeInfo = this._elementTypeInfo.Value;
 7812            var delimByteCount = encoding.Encoding.GetByteCount( boundData.ArrayDelimiter );
 7813            var curIdx = 0;
 62814            foreach ( var element in value )
 815            {
 24816               var elementSizeInfo = elementSizeInfos[eIdx++];
 24817               if ( element == null )
 818               {
 819                  // Write 'NULL'
 4820                  stream.AppendToBytes( asciiSize * NULL_CHAR_COUNT, ( bArray, idx, count ) => encoding.Encoding.GetByte
 2821                  await stream.FlushAsync();
 2822               }
 823               else
 824               {
 22825                  using ( var elementStream = await stream.CreateWithLimitedSizeAndSharedBuffer( elementSizeInfo.ByteCou
 826                  {
 22827                     await elementTypeInfo.Functionality.WriteBackendValueAsync(
 22828                        DataFormat.Text,
 22829                        elementTypeInfo.DatabaseData,
 22830                        helper,
 22831                        elementStream,
 22832                        element,
 22833                        elementSizeInfo,
 22834                        true
 22835                        );
 22836                     await elementStream.FlushAsync();
 22837                  }
 838               }
 839
 24840               if ( curIdx++ < length - 1 )
 841               {
 17842                  if ( indices == null )
 843                  {
 2844                     stream.AppendToBytes( delimByteCount, ( bArray, idx, count ) => encoding.Encoding.GetBytes( boundDa
 1845                  }
 846                  else
 847                  {
 848                     // We might need to write '}'s
 16849                     var amountOfDimensionsEnded = MoveNextMultiDimensionalIndex( lengths, indices );
 850                     // Have to write '}'s, followed by array separator, followed by equally many '{'s.
 16851                     stream.AppendToBytes( amountOfDimensionsEnded * 2 * asciiSize + delimByteCount, ( bArray, idx, coun
 16852                     {
 62853                        for ( var i = 0; i < amountOfDimensionsEnded; ++i )
 16854                        {
 23855                           encoding.WriteASCIIByte( bArray, ref idx, ARRAY_END );
 16856                        }
 32857                        idx += encoding.Encoding.GetBytes( boundData.ArrayDelimiter, 0, boundData.ArrayDelimiter.Length,
 62858                        for ( var i = 0; i < amountOfDimensionsEnded; ++i )
 16859                        {
 23860                           encoding.WriteASCIIByte( bArray, ref idx, ARRAY_START );
 16861                        }
 32862                     } );
 863                  }
 864
 17865                  await stream.FlushAsync();
 866               }
 24867            }
 868
 869            // Write final '}'s
 7870            stream.AppendToBytes( rankASCIISize, ( bArray, idx, count ) =>
 7871            {
 43872               for ( var i = 0; i < rank; ++i )
 7873               {
 18874                  encoding.WriteASCIIByte( bArray, ref idx, ARRAY_END );
 7875               }
 14876            } );
 7877         }
 8878         await stream.FlushAsync();
 8879      }
 880
 881      private async Task WriteArrayBinary(
 882         PgSQLTypeDatabaseData boundData,
 883         BackendABIHelper helper,
 884         StreamWriterWithResizableBufferAndLimitedSize stream,
 885         Array value,
 886         BackendSizeInfo sizeInfo
 887         )
 888      {
 889         // Write header (3 integers + 2 integers per rank)
 20890         var elemInfo = this._elementTypeInfo.Value;
 24891         var arrayLength = value.Length;
 24892         var rank = arrayLength <= 0 ? 0 : value.Rank;
 24893         stream.AppendToBytes( ( 3 + 2 * rank ) * sizeof( Int32 ), ( bArray, idx, count ) =>
 24894           {
 44895              bArray
 44896                 .WritePgInt32( ref idx, rank )
 44897                 .WritePgInt32( ref idx, 1 ) // null map, always zero in our case
 44898                 .WritePgInt32( ref idx, elemInfo.DatabaseData.TypeID );
 136899              for ( var i = 0; i < rank; ++i )
 24900              {
 56901                 bArray
 56902                    .WritePgInt32( ref idx, value.GetLength( i ) )
 56903                    .WritePgInt32( ref idx, value.GetLowerBound( i ) + 1 ); // SQL lower bounds for arrays are 1 by defa
 24904              }
 48905           } );
 906
 907         // Send header
 24908         await stream.FlushAsync();
 909
 23910         if ( arrayLength > 0 )
 911         {
 912            // Send elements
 21913            var additionalSizeInfo = (BackendSizeInfo[]) sizeInfo.CustomInformation;
 18914            var j = 0;
 175915            foreach ( var element in value )
 916            {
 70917               var elementSizeInfo = additionalSizeInfo[j++];
 69918               using ( var elementStream = await stream.CreateWithLimitedSizeAndSharedBuffer(
 69919                  Math.Max( 0, elementSizeInfo.ByteCount ) + sizeof( Int32 )
 69920                  ) )
 921               {
 68922                  await elemInfo.Functionality.WriteBackendValueCheckNull(
 68923                     DataFormat.Binary,
 68924                     elemInfo.DatabaseData,
 68925                     helper,
 68926                     elementStream,
 68927                     element,
 68928                     elementSizeInfo,
 68929                     true
 68930                     );
 72931               }
 71932            }
 20933         }
 23934      }
 935
 936   }
 937}