/// <summary> /// Creates an n-dimensional System.Array from PG binary representation. /// This function reads the array header and sets up an n-dimensional System.Array object to hold its data. /// PopulateArrayFromBinaryArray() is then called to carry out array population. /// </summary> public object ArrayBinaryToArray(NpgsqlBackendTypeInfo TypeInfo, byte[] BackendData, Int32 fieldValueSize, Int32 TypeModifier) { // Sanity check. if (BackendData.Length < 4) { throw new Exception("Insuffient backend data to describe dimension count in binary array header"); } // Offset 0 holds an integer dscribing the number of dimensions in the array. int nDims = PGUtil.ReadInt32(BackendData, 0); // Sanity check. if (nDims < 0) { throw new NpgsqlException("Invalid array dimension count encountered in binary array header"); } // {PG handles 0-dimension arrays, but .net does not. Return a 0-size 1-dimensional array. if (nDims == 0) { return(Array.CreateInstance(_elementConverter.FrameworkType, 0)); } int dimOffset; // Offset 12 begins an array of {int,int} objects, of length nDims. int dataOffset = 12; // Sanity check. if (BackendData.Length < dataOffset + nDims * 8) { throw new NpgsqlException("Insuffient backend data to describe all expected dimensions in binary array header"); } int[] dimLengths; int[] dimLBounds; dimLengths = new int[nDims]; dimLBounds = new int[nDims]; // Populate array dimension lengths and lower bounds. for (dimOffset = 0; dimOffset < nDims; dimOffset++) { dimLengths[dimOffset] = PGUtil.ReadInt32(BackendData, dataOffset); dataOffset += 4; // Lower bounds is 1-based in SQL, 0-based in .NET. dimLBounds[dimOffset] = PGUtil.ReadInt32(BackendData, dataOffset) - 1; dataOffset += 4; } Array dst; int[] dstOffsets; dst = Array.CreateInstance(_elementConverter.FrameworkType, dimLengths, dimLBounds); dstOffsets = new int[nDims]; // Right after the dimension descriptors begins array data. // Populate the new array. PopulateArrayFromBinaryArray(TypeInfo, BackendData, fieldValueSize, TypeModifier, ref dataOffset, dimLengths, dimLBounds, 0, dst, dstOffsets); return(dst); }
/// <summary> /// Recursively populates an array from PB binary data representation. /// </summary> private void PopulateArrayFromBinaryArray(NpgsqlBackendTypeInfo TypeInfo, byte[] backendData, Int32 fieldValueSize, Int32 TypeModifier, ref int dataOffset, int[] dimLengths, int[] dimLBounds, int dimOffset, Array dst, int[] dstOffsets) { int dimensionLBound = dimLBounds[dimOffset]; int end = dimensionLBound + dimLengths[dimOffset]; if (dimOffset < dimLengths.Length - 1) { // Drill down recursively until we hit a single dimension array. for (int i = dimensionLBound; i < end; i++) { dstOffsets[dimOffset] = i; PopulateArrayFromBinaryArray(TypeInfo, backendData, fieldValueSize, TypeModifier, ref dataOffset, dimLengths, dimLBounds, dimOffset + 1, dst, dstOffsets); } } else { // Populate a single dimension array. for (int i = dimensionLBound; i < end; i++) { // Sanity check. if (backendData.Length < dataOffset + 4) { throw new NpgsqlException("Out of backend data while reading binary array"); } int elementLength; // Each element consists of an int length identifier, followed by that many bytes of raw data. // Length -1 indicates a NULL value, and is naturally followed by no data. elementLength = PGUtil.ReadInt32(backendData, dataOffset); dataOffset += 4; if (elementLength == -1) { // This currently throws an exception on value types. dst.SetValue(DBNull.Value, dstOffsets); } else { // Sanity check. if (backendData.Length < dataOffset + elementLength) { throw new NpgsqlException("Out of backend data while reading binary array"); } byte[] elementBinary; // Get element data from backend data. elementBinary = PGUtil.ReadBytes(backendData, dataOffset, elementLength); object elementNative; elementNative = _elementConverter.ConvertBackendBinaryToNative(elementBinary, fieldValueSize, TypeModifier); dstOffsets[dimOffset] = i; dst.SetValue(elementNative, dstOffsets); dataOffset += elementLength; } } } }
/// <summary> /// Create a new ArrayBackendToNativeTypeConverter /// </summary> /// <param name="elementConverter"><see cref="NpgsqlBackendTypeInfo"/> for the element type.</param> public ArrayBackendToNativeTypeConverter(NpgsqlBackendTypeInfo elementConverter) { _elementConverter = elementConverter; }