X Tutup
#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.Generic; using System.Diagnostics; 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 { [TypeMapping("text", NpgsqlDbType.Text, new[] { DbType.String, DbType.StringFixedLength, DbType.AnsiString, DbType.AnsiStringFixedLength }, new[] { typeof(string), typeof(char[]), typeof(char) }, DbType.String )] [TypeMapping("xml", NpgsqlDbType.Xml, dbType: DbType.Xml)] [TypeMapping("varchar", NpgsqlDbType.Varchar, inferredDbType: DbType.String)] [TypeMapping("bpchar", NpgsqlDbType.Char, inferredDbType: DbType.String)] [TypeMapping("name", NpgsqlDbType.Name, inferredDbType: DbType.String)] [TypeMapping("json", NpgsqlDbType.Json, inferredDbType: DbType.String)] [TypeMapping("refcursor", NpgsqlDbType.Refcursor, inferredDbType: DbType.String)] [TypeMapping("citext", NpgsqlDbType.Citext, inferredDbType: DbType.String)] [TypeMapping("unknown")] internal class TextHandler : ChunkingTypeHandler, IChunkingTypeHandler { internal override bool PreferTextWrite => true; #region State string _str; char[] _chars; byte[] _tempBuf; int _byteLen, _charLen, _bytePos, _charPos; NpgsqlBuffer _buf; readonly char[] _singleCharArray = new char[1]; #endregion #region Read internal virtual void PrepareRead(NpgsqlBuffer buf, FieldDescription fieldDescription, int len) { _buf = buf; _byteLen = len; _bytePos = -1; } public override void PrepareRead(NpgsqlBuffer buf, int len, FieldDescription fieldDescription) { PrepareRead(buf, fieldDescription, len); } public override bool Read([CanBeNull] out string result) { if (_bytePos == -1) { if (_byteLen <= _buf.ReadBytesLeft) { // Already have the entire string in the buffer, decode and return result = _buf.ReadString(_byteLen); _buf = null; return true; } if (_byteLen <= _buf.UsableSize) { // Don't have the entire string in the buffer, but it can fit. Force a read to fill. result = null; return false; } // Bad case: the string doesn't fit in our buffer. // Allocate a temporary byte buffer to hold the entire string and read it in chunks. // TODO: Pool/recycle the buffer? _tempBuf = new byte[_byteLen]; _bytePos = 0; } var len = Math.Min(_buf.ReadBytesLeft, _byteLen - _bytePos); _buf.ReadBytes(_tempBuf, _bytePos, len); _bytePos += len; if (_bytePos < _byteLen) { result = null; return false; } result = _buf.TextEncoding.GetString(_tempBuf); _tempBuf = null; _buf = null; return true; } public bool Read([CanBeNull] out char[] result) { if (_bytePos == -1) { if (_byteLen <= _buf.ReadBytesLeft) { // Already have the entire string in the buffer, decode and return result = _buf.ReadChars(_byteLen); _buf = null; return true; } if (_byteLen <= _buf.UsableSize) { // Don't have the entire string in the buffer, but it can fit. Force a read to fill. result = null; return false; } // Bad case: the string doesn't fit in our buffer. // Allocate a temporary byte buffer to hold the entire string and read it in chunks. // TODO: Pool/recycle the buffer? _tempBuf = new byte[_byteLen]; _bytePos = 0; } var len = Math.Min(_buf.ReadBytesLeft, _byteLen - _bytePos); _buf.ReadBytes(_tempBuf, _bytePos, len); _bytePos += len; if (_bytePos < _byteLen) { result = null; return false; } result = _buf.TextEncoding.GetChars(_tempBuf); _tempBuf = null; _buf = null; return true; } public long GetChars(DataRowMessage row, int charOffset, [CanBeNull] char[] output, int outputOffset, int charsCount, FieldDescription field) { if (row.PosInColumn == 0) { _charPos = 0; } if (output == null) { // Note: Getting the length of a text column means decoding the entire field, // very inefficient and also consumes the column in sequential mode. But this seems to // be SqlClient's behavior as well. int bytesSkipped, charsSkipped; row.Buffer.SkipChars(int.MaxValue, row.ColumnLen - row.PosInColumn, out bytesSkipped, out charsSkipped); Contract.Assume(bytesSkipped == row.ColumnLen - row.PosInColumn); row.PosInColumn += bytesSkipped; _charPos += charsSkipped; return _charPos; } if (charOffset < _charPos) { row.SeekInColumn(0); _charPos = 0; } if (charOffset > _charPos) { var charsToSkip = charOffset - _charPos; int bytesSkipped, charsSkipped; row.Buffer.SkipChars(charsToSkip, row.ColumnLen - row.PosInColumn, out bytesSkipped, out charsSkipped); row.PosInColumn += bytesSkipped; _charPos += charsSkipped; if (charsSkipped < charsToSkip) { // TODO: What is the actual required behavior here? throw new IndexOutOfRangeException(); } } int bytesRead, charsRead; row.Buffer.ReadAllChars(output, outputOffset, charsCount, row.ColumnLen - row.PosInColumn, out bytesRead, out charsRead); row.PosInColumn += bytesRead; _charPos += charsRead; return charsRead; } #endregion #region Write public override int ValidateAndGetLength(object value, ref LengthCache lengthCache, NpgsqlParameter parameter = null) { if (lengthCache == null) { lengthCache = new LengthCache(1); } if (lengthCache.IsPopulated) { return lengthCache.Get(); } //return lengthCache.Set(DoValidateAndGetLength(value, parameter)); var asString = value as string; if (asString != null) { return lengthCache.Set( parameter == null || parameter.Size <= 0 || parameter.Size >= asString.Length ? PGUtil.UTF8Encoding.GetByteCount(asString) : PGUtil.UTF8Encoding.GetByteCount(asString.ToCharArray(), 0, parameter.Size) ); } var asCharArray = value as char[]; if (asCharArray != null) { return lengthCache.Set( parameter == null || parameter.Size <= 0 || parameter.Size >= asCharArray.Length ? PGUtil.UTF8Encoding.GetByteCount(asCharArray) : PGUtil.UTF8Encoding.GetByteCount(asCharArray, 0, parameter.Size) ); } if (value is char) { _singleCharArray[0] = (char)value; return lengthCache.Set(PGUtil.UTF8Encoding.GetByteCount(_singleCharArray)); } // Fallback - try to convert the value to string var converted = Convert.ToString(value); if (parameter == null) { throw CreateConversionButNoParamException(value.GetType()); } parameter.ConvertedValue = converted; return lengthCache.Set( parameter.Size <= 0 || parameter.Size >= converted.Length ? PGUtil.UTF8Encoding.GetByteCount(converted) : PGUtil.UTF8Encoding.GetByteCount(converted.ToCharArray(), 0, parameter.Size) ); } public override void PrepareWrite(object value, NpgsqlBuffer buf, LengthCache lengthCache, NpgsqlParameter parameter=null) { _buf = buf; _charPos = -1; _byteLen = lengthCache.GetLast(); if (parameter?.ConvertedValue != null) { value = parameter.ConvertedValue; } _str = value as string; if (_str != null) { _charLen = parameter == null || parameter.Size <= 0 || parameter.Size >= _str.Length ? _str.Length : parameter.Size; return; } _chars = value as char[]; if (_chars != null) { _charLen = parameter == null || parameter.Size <= 0 || parameter.Size >= _chars.Length ? _chars.Length : parameter.Size; return; } if (value is char) { _singleCharArray[0] = (char)value; _chars = _singleCharArray; _charLen = 1; return; } _str = Convert.ToString(value); _charLen = parameter == null || parameter.Size <= 0 || parameter.Size >= _str.Length ? _str.Length : parameter.Size; } public override bool Write(ref DirectBuffer directBuf) { if (_charPos == -1) { if (_byteLen <= _buf.WriteSpaceLeft) { // Can simply write the string to the buffer if (_str != null) { _buf.WriteString(_str, _charLen); _str = null; } else { Contract.Assert(_chars != null); _buf.WriteChars(_chars, _charLen); _str = null; } _buf = null; return true; } if (_byteLen <= _buf.UsableSize) { // Buffer is currently too full, but the string can fit. Force a write to fill. return false; } // Bad case: the string doesn't fit in our buffer. _charPos = 0; // For strings, chunked/incremental conversion isn't supported // (see https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/6584398-add-system-text-encoder-convert-method-string-in) // So for now allocate a temporary byte buffer to hold the entire string and write it directly. if (_str != null) { directBuf.Buffer = new byte[_byteLen]; _buf.TextEncoding.GetBytes(_str, 0, _charLen, directBuf.Buffer, 0); return false; } Contract.Assert(_chars != null); // For char arrays, fall through to chunked writing below } if (_str != null) { // We did a direct buffer write above, and must now clean up _str = null; _buf = null; return true; } int charsUsed; bool completed; _buf.WriteStringChunked(_chars, _charPos, _chars.Length - _charPos, false, out charsUsed, out completed); if (completed) { // Flush encoder _buf.WriteStringChunked(_chars, _charPos, _chars.Length - _charPos, true, out charsUsed, out completed); _chars = null; _buf = null; return true; } _charPos += charsUsed; return false; } #endregion } }
X Tutup