See More

#region License // The PostgreSQL License // // Copyright (C) 2015 The Npgsql Development Team // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph and the following two paragraphs appear in all copies. // // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. // // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. #endregion using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Text; using Npgsql.BackendMessages; using NpgsqlTypes; using System.Data; using JetBrains.Annotations; namespace Npgsql.TypeHandlers { ///

/// Handler for the PostgreSQL bit string type. /// Note that for BIT(1), this handler will return a bool by default, to align with SQLClient /// (see discussion https://github.com/npgsql/npgsql/pull/362#issuecomment-59622101). /// /// /// http://www.postgresql.org/docs/current/static/datatype-bit.html /// [TypeMapping("varbit", NpgsqlDbType.Varbit, typeof(BitArray))] [TypeMapping("bit", NpgsqlDbType.Bit)] internal class BitStringHandler : ChunkingTypeHandler, IChunkingTypeHandler { NpgsqlBuffer _buf; int _len; BitArray _bitArray; object _value; int _pos; bool _isSingleBit; internal override Type GetFieldType(FieldDescription fieldDescription) { return fieldDescription != null && fieldDescription.TypeModifier == 1 ? typeof (bool) : typeof(BitArray); } internal override Type GetProviderSpecificFieldType(FieldDescription fieldDescription) { return GetFieldType(fieldDescription); } internal override object ReadValueAsObjectFully(DataRowMessage row, FieldDescription fieldDescription) { return fieldDescription.TypeModifier == 1 ? (object)ReadFully(row, row.ColumnLen, fieldDescription) : ReadFully(row, row.ColumnLen, fieldDescription); } internal override object ReadPsvAsObjectFully(DataRowMessage row, FieldDescription fieldDescription) { return ReadValueAsObjectFully(row, fieldDescription); } #region Read public override void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription) { _isSingleBit = fieldDescription.TypeModifier == 1; _buf = buf; _pos = -1; _len = len - 4; // Subtract leading bit length field } bool IChunkingTypeHandler.Read(out bool result) { result = false; if (!_isSingleBit) { throw new InvalidCastException("Can't convert a BIT(N) type to bool, only BIT(1)"); } if (_buf.ReadBytesLeft < 4) { return false; } var bitLen = _buf.ReadInt32(); Contract.Assume(bitLen == 1); var b = _buf.ReadByte(); result = (b & 128) != 0; return true; } /// /// Reads a BitArray from a binary PostgreSQL value. First 32-bit big endian length, /// then the data in big-endian. Zero-padded low bits in the end if length is not multiple of 8. /// public override bool Read([CanBeNull] out BitArray result) { if (_pos == -1) { if (_buf.ReadBytesLeft < 4) { result = null; return false; } var numBits = _buf.ReadInt32(); _bitArray = new BitArray(numBits); _pos = 0; } var bitNo = _pos * 8; var maxBytes = _pos + Math.Min(_len - _pos - 1, _buf.ReadBytesLeft); for (; _pos < maxBytes; _pos++) { var chunk = _buf.ReadByte(); _bitArray[bitNo++] = (chunk & (1 << 7)) != 0; _bitArray[bitNo++] = (chunk & (1 << 6)) != 0; _bitArray[bitNo++] = (chunk & (1 << 5)) != 0; _bitArray[bitNo++] = (chunk & (1 << 4)) != 0; _bitArray[bitNo++] = (chunk & (1 << 3)) != 0; _bitArray[bitNo++] = (chunk & (1 << 2)) != 0; _bitArray[bitNo++] = (chunk & (1 << 1)) != 0; _bitArray[bitNo++] = (chunk & (1 << 0)) != 0; } if (_pos < _len - 1) { result = null; return false; } if (bitNo < _bitArray.Length) { var remainder = _bitArray.Length - bitNo; var lastChunk = _buf.ReadByte(); for (var i = 7; i >= 8 - remainder; i--) { _bitArray[bitNo++] = (lastChunk & (1 << i)) != 0; } } result = _bitArray; return true; } #endregion #region Write public override int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter=null) { var asBitArray = value as BitArray; if (asBitArray != null) return 4 + (asBitArray.Length + 7) / 8; if (value is bool) return 5; var asString = value as string; if (asString != null) { if (asString.Any(c => c != '0' && c != '1')) throw new FormatException("Cannot interpret as ASCII BitString: " + asString); return 4 + (asString.Length + 7)/8; } throw CreateConversionException(value.GetType()); } public override void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter=null) { _buf = buf; _pos = -1; _value = value; } public override bool Write(ref DirectBuffer directBuf) { var bitArray = _value as BitArray; if (bitArray != null) { return WriteBitArray(bitArray); } if (_value is bool) { return WriteBool((bool)_value); } var str = _value as string; if (str != null) { return WriteString(str); } throw PGUtil.ThrowIfReached($"Bad type {_value.GetType()} some made its way into BitStringHandler.Write()"); } bool WriteBitArray(BitArray bitArray) { if (_pos < 0) { // Initial bitlength byte if (_buf.WriteSpaceLeft < 4) { return false; } _buf.WriteInt32(bitArray.Length); _pos = 0; } var byteLen = (bitArray.Length + 7) / 8; var endPos = _pos + Math.Min(byteLen - _pos, _buf.WriteSpaceLeft); for (; _pos < endPos; _pos++) { var bitPos = _pos * 8; var b = 0; for (var i = 0; i < Math.Min(8, bitArray.Length - bitPos); i++) b += (bitArray[bitPos + i] ? 1 : 0) << (8 - i - 1); _buf.WriteByte((byte)b); } if (_pos < byteLen) { return false; } _buf = null; _value = null; return true; } bool WriteBool(bool b) { if (_buf.WriteSpaceLeft < 5) return false; _buf.WriteInt32(1); _buf.WriteByte(b ? (byte)0x80 : (byte)0); _buf = null; _value = null; return true; } bool WriteString(string str) { if (_pos < 0) { // Initial bitlength byte if (_buf.WriteSpaceLeft < 4) { return false; } _buf.WriteInt32(str.Length); _pos = 0; } var byteLen = (str.Length + 7) / 8; var bytePos = (_pos + 7) / 8; var endBytePos = bytePos + Math.Min(byteLen - bytePos - 1, _buf.WriteSpaceLeft); for (; bytePos < endBytePos; bytePos++) { var b = 0; b += (str[_pos++] - '0') << 7; b += (str[_pos++] - '0') << 6; b += (str[_pos++] - '0') << 5; b += (str[_pos++] - '0') << 4; b += (str[_pos++] - '0') << 3; b += (str[_pos++] - '0') << 2; b += (str[_pos++] - '0') << 1; b += (str[_pos++] - '0'); _buf.WriteByte((byte)b); } if (bytePos < byteLen - 1) { return false; } if (_pos < str.Length) { var remainder = str.Length - _pos; var lastChunk = 0; for (var i = 7; i >= 8 - remainder; i--) { lastChunk += (str[_pos++] - '0') << i; } _buf.WriteByte((byte)lastChunk); } _buf = null; _value = null; return true; } #endregion } /// /// A special handler for arrays of bit strings. /// Differs from the standard array handlers in that it returns arrays of bool for BIT(1) and arrays /// of BitArray otherwise (just like the scalar BitStringHandler does). /// internal class BitStringArrayHandler : ArrayHandler { FieldDescription _fieldDescription; object _value; public override Type GetElementFieldType(FieldDescription fieldDescription) { return fieldDescription.TypeModifier == 1 ? typeof(bool) : typeof(BitArray); } public override Type GetElementPsvType(FieldDescription fieldDescription) { return GetElementFieldType(fieldDescription); } public BitStringArrayHandler(BitStringHandler elementHandler) : base(elementHandler) {} public override void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription) { base.PrepareRead(buf, len, fieldDescription); _fieldDescription = fieldDescription; } public override bool Read(out Array result) { return _fieldDescription.TypeModifier == 1 ? Read(out result) : Read(out result); } public override void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter=null) { base.PrepareWrite(value, buf, lengthCache, parameter); _value = value; } public override bool Write(ref DirectBuffer directBuf) { if (_value is BitArray[]) { return base.Write(ref directBuf); } if (_value is bool[]) { return base.Write(ref directBuf); } if (_value is string[]) { return base.Write(ref directBuf); } throw PGUtil.ThrowIfReached($"Can't write type {_value.GetType()} as an bitstring array"); } public override int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter=null) { if (value is BitArray[]) { return base.ValidateAndGetLength(value, ref lengthCache, parameter); } if (value is bool[]) { return base.ValidateAndGetLength(value, ref lengthCache, parameter); } if (value is string[]) { return base.ValidateAndGetLength(value, ref lengthCache, parameter); } throw new InvalidCastException($"Can't write type {value.GetType()} as an bitstring array"); } } }