public override async ValueTask <Object> ReadBackendValue( DataFormat dataFormat, PgSQLTypeDatabaseData boundData, BackendABIHelper helper, StreamReaderWithResizableBufferAndLimitedSize stream ) { Array retVal; switch (dataFormat) { case DataFormat.Text: if (stream.TotalByteCount > 2 * helper.Encoding.BytesPerASCIICharacter) { retVal = await this.ReadArrayText(boundData, helper, stream); } else { // Empty array retVal = this._emptyArray; } break; case DataFormat.Binary: retVal = await this.ReadArrayBinary(boundData, helper, stream); break; default: throw new NotSupportedException($"Data format {dataFormat} is not recognized."); } return(retVal); }
private async ValueTask <(Int32 LowestEncounteredArrayEnd, Int32[] Lengths, Boolean HasMore)> ReadArrayTextDimensionEnd( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, StreamReaderWithResizableBufferAndLimitedSize stream, Int32 lowestEncounteredArrayEnd, Int32[] lengths, Int32 rank ) { stream.EraseReadBytesFromBuffer(); var wasArrayEnd = true; var innermostArrayIndex = rank - 1; var curArrayIndex = innermostArrayIndex; Char curChar; var charReader = helper.CharacterReader; while (wasArrayEnd && --curArrayIndex >= 0) { // End current array block lowestEncounteredArrayEnd = Math.Min(lowestEncounteredArrayEnd, curArrayIndex); if (curArrayIndex <= lowestEncounteredArrayEnd) { lengths[curArrayIndex]++; } // Read next character curChar = await charReader.ReadNextCharacterAsync(stream); wasArrayEnd = curChar == ARRAY_END; } var hasMore = curArrayIndex >= 0; if (hasMore) { // More arrays follow // Read until we are at innermost array level again while (curArrayIndex < innermostArrayIndex) { curChar = await charReader.ReadNextCharacterAsync(stream); if (curChar == ARRAY_START) { ++curArrayIndex; } } } stream.EraseReadBytesFromBuffer(); return(lowestEncounteredArrayEnd, lengths, hasMore); }
public override async ValueTask <Object> ReadBackendValue( DataFormat dataFormat, PgSQLTypeDatabaseData boundData, BackendABIHelper helper, StreamReaderWithResizableBufferAndLimitedSize stream ) { // No truly async support for deserializing JTokens in Newtonsoft, at least yet, so let's do it ourselves switch (dataFormat) { case DataFormat.Text: return(await stream.ReadJSONTTokenAsync(helper.CharacterReader)); case DataFormat.Binary: throw new InvalidOperationException("This data format is not supported"); default: throw new NotSupportedException($"Unrecognized data format: ${dataFormat}."); } }
// We never encounter empty arrays when calling this, since inner empty arrays are not possible, and whole empty array string is handled separately private async ValueTask <(Object Value, ElementEndingWay Ending)> ReadArrayElementText( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, StreamReaderWithResizableBufferAndLimitedSize stream, (PgSQLTypeFunctionality UnboundInfo, PgSQLTypeDatabaseData BoundData) elementTypeInfo
private async ValueTask <(Int32 Rank, Int32[] Lobos, Int32[] Lengths, Array CreatedArray)> ReadArrayTextHeader( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, StreamReaderWithResizableBufferAndLimitedSize stream ) { stream.EraseReadBytesFromBuffer(); Char curChar; // Read the optional "[lobo1:upbo1][lobo2:upbo2]...=" dimension specification into buffer. var rank = 0; var charReader = helper.CharacterReader; do { curChar = await charReader.ReadNextCharacterAsync(stream); if (curChar == DIM_SEPARATOR) { ++rank; } } while (curChar != ARRAY_START); Int32[] lobos = null; Int32[] lengths = null; Array retVal = null; if (rank > 0) { // We encountered the explicit dimension specification. Backend should issue this only when there are 'special' lower bounds. // As a bonus, we will know the array dimensions before array elements start, and we can create the array to be returned right away, // instead of reading elements into temporary array lobos = new Int32[rank]; lengths = new Int32[rank]; var encoding = helper.Encoding; var asciiSize = encoding.BytesPerASCIICharacter; var byteArray = stream.Buffer; var idx = asciiSize; // Skip first '[' for (var i = 0; i < rank; ++i) { // In (Pg)SQL, lower bounds normally start at 1. So 1 translates to 0 in CLR, 0 to -1, etc. lobos[i] = encoding.ParseInt32Textual(byteArray, ref idx) - 1; idx += asciiSize; // Skip ':' lengths[i] = encoding.ParseInt32Textual(byteArray, ref idx) - lobos[i]; idx += asciiSize * 2; // Skip ']' and next '[' or '=' } retVal = Array.CreateInstance(this._arrayElementType, lengths, lobos); } // Read amount of starting '{' characters. That will be the array rank (unless we already learned about the rank in the dimension specification header). rank = 0; Int32 prevIdx; do { ++rank; prevIdx = stream.ReadBytesCount; curChar = await charReader.ReadNextCharacterAsync(stream); } while (curChar == ARRAY_START); if (retVal != null && rank != retVal.Rank) { throw new PgSQLException("Backend array lower-bound specification had different rank than actual array specifciation."); } // Back one character (the one we read, that wasn't array start character) stream.UnreadBytes(stream.ReadBytesCount - prevIdx); // Remember to get rid of array start characters currently in buffer (ReadArrayElementText expects clean buffer start) stream.EraseReadBytesFromBuffer(); return(rank, lobos, lengths, retVal); }
private async Task <Array> ReadArrayText( PgSQLTypeDatabaseData boundData, BackendABIHelper helper, StreamReaderWithResizableBufferAndLimitedSize stream ) { (var rank, var lobos, var lengths, var retVal) = await this.ReadArrayTextHeader(boundData, helper, stream); // Use exponentially expanding array instead of list. That way we can use Array.Copy right away when creating rank-1 array. var useTempArray = retVal == null; var totalCount = 0; // We only need temporary array if we are creating array without dimension specification prefix Int32[] retValIndices; ResizableArray <Object> tempArray; Int32 arrayDelimiterByteCount; if (useTempArray) { tempArray = new ResizableArray <Object>(initialSize: 2, exponentialResize: true); lengths = new Int32[rank]; retValIndices = null; arrayDelimiterByteCount = -1; } else { retValIndices = new Int32[rank]; Array.Copy(lobos, retValIndices, rank); tempArray = null; arrayDelimiterByteCount = helper.Encoding.Encoding.GetByteCount(boundData.ArrayDelimiter); } // We start with innermost array var innermostArrayIndex = rank - 1; var lowestEncounteredArrayEnd = innermostArrayIndex; var elemTypeInfo = this.GetElementTypeInfo(); var asciiSize = helper.Encoding.BytesPerASCIICharacter; Boolean hasMore = true; while (hasMore) { Object value; ElementEndingWay ending; (value, ending) = await this.ReadArrayElementText(boundData, helper, stream, elemTypeInfo); if (useTempArray) { tempArray.CurrentMaxCapacity = totalCount + 1; tempArray.Array[totalCount++] = value; if (lowestEncounteredArrayEnd == innermostArrayIndex) { ++lengths[innermostArrayIndex]; } // If array end encountered, we must find start of next element, if possible if (ending == ElementEndingWay.ArrayEnd) { (lowestEncounteredArrayEnd, lengths, hasMore) = await this.ReadArrayTextDimensionEnd( boundData, helper, stream, lowestEncounteredArrayEnd, lengths, rank ); } } else { retVal.SetValue(value, retValIndices); var dimsEnded = MoveNextMultiDimensionalIndex(lengths, retValIndices, lobos); // At this point, the ReadArrayElementText method has already read either complete array delimiter, or one array end character // So we only need to skip thru bytes if we have read array end character (dimsEnded > 0) if (dimsEnded > 0) { hasMore = dimsEnded < rank; if (hasMore) { // Skip thru array end, array delimiter, and array start characters await stream.ReadMoreOrThrow((dimsEnded * 2 - 1) *asciiSize + arrayDelimiterByteCount); } } } stream.EraseReadBytesFromBuffer(); } // Now, construct the actual array to return, if needed // If array to be returned is null at this stage, this means that no lower bound specifications were given. if (retVal == null) { if (rank == 1) { // Create normal one-dimensional array (we always need to create it, since we must return X[] instead of Object[]) retVal = Array.CreateInstance(this._arrayElementType, totalCount); // Populate it Array.Copy(tempArray.Array, retVal, totalCount); } else { // Create multi-dimensional array retVal = Array.CreateInstance(this._arrayElementType, lengths); // Populate it var curIndices = new Int32[lengths.Length]; var idx = 0; var actualArray = tempArray.Array; do { var elem = actualArray[idx++]; if (elem != null) { retVal.SetValue(elem, curIndices); } MoveNextMultiDimensionalIndex(lengths, curIndices); } while (idx < totalCount); } } return(retVal); }