Exemple #1
0
        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);
        }
Exemple #2
0
        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);
        }
Exemple #3
0
        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}.");
            }
        }
Exemple #4
0
 // 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
Exemple #5
0
        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);
        }
Exemple #6
0
        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);
        }