-
Notifications
You must be signed in to change notification settings - Fork 874
Expand file tree
/
Copy pathBitStringConverters.cs
More file actions
239 lines (202 loc) · 9.16 KB
/
BitStringConverters.cs
File metadata and controls
239 lines (202 loc) · 9.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Npgsql.Internal.Postgres;
using static Npgsql.Internal.Converters.BitStringHelpers;
namespace Npgsql.Internal.Converters;
file static class BitStringHelpers
{
public static int GetByteCountFromBitCount(int n)
{
const int BitShiftPerByte = 3;
Debug.Assert(n >= 0);
// Due to sign extension, we don't need to special case for n == 0, since ((n - 1) >> 3) + 1 = 0
// This doesn't hold true for ((n - 1) / 8) + 1, which equals 1.
return (n - 1 + (1 << BitShiftPerByte)) >>> BitShiftPerByte;
}
}
sealed class BitArrayBitStringConverter : PgStreamingConverter<BitArray>
{
public override BitArray Read(PgReader reader)
{
if (reader.ShouldBuffer(sizeof(int)))
reader.Buffer(sizeof(int));
var bits = reader.ReadInt32();
var bytes = new byte[GetByteCountFromBitCount(bits)];
reader.ReadBytes(bytes);
return ReadValue(bytes, bits);
}
public override async ValueTask<BitArray> ReadAsync(PgReader reader, CancellationToken cancellationToken = default)
{
if (reader.ShouldBuffer(sizeof(int)))
await reader.BufferAsync(sizeof(int), cancellationToken).ConfigureAwait(false);
var bits = reader.ReadInt32();
var bytes = new byte[GetByteCountFromBitCount(bits)];
await reader.ReadBytesAsync(bytes, cancellationToken).ConfigureAwait(false);
return ReadValue(bytes, bits);
}
internal static BitArray ReadValue(byte[] bytes, int bits)
{
for (var i = 0; i < bytes.Length; i++)
{
ref var b = ref bytes[i];
b = ReverseBits(b);
}
return new(bytes) { Length = bits };
// https://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits
static byte ReverseBits(byte b) => (byte)(((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL >> 32);
}
public override Size GetSize(SizeContext context, BitArray value, ref object? writeState)
=> sizeof(int) + GetByteCountFromBitCount(value.Length);
public override void Write(PgWriter writer, BitArray value)
=> Write(async: false, writer, value, CancellationToken.None).GetAwaiter().GetResult();
public override ValueTask WriteAsync(PgWriter writer, BitArray value, CancellationToken cancellationToken = default)
=> Write(async: true, writer, value, cancellationToken);
async ValueTask Write(bool async, PgWriter writer, BitArray value, CancellationToken cancellationToken = default)
{
var byteCount = writer.Current.Size.Value - sizeof(int);
var array = ArrayPool<byte>.Shared.Rent(byteCount);
for (var pos = 0; pos < byteCount; pos++)
{
var bitPos = pos*8;
var bits = Math.Min(8, value.Length - bitPos);
var b = 0;
for (var i = 0; i < bits; i++)
b += (value[bitPos + i] ? 1 : 0) << (8 - i - 1);
array[pos] = (byte)b;
}
if (writer.ShouldFlush(sizeof(int)))
await writer.Flush(async, cancellationToken).ConfigureAwait(false);
writer.WriteInt32(value.Length);
if (async)
await writer.WriteBytesAsync(new ReadOnlyMemory<byte>(array, 0, byteCount), cancellationToken).ConfigureAwait(false);
else
writer.WriteBytes(new ReadOnlySpan<byte>(array, 0, byteCount));
ArrayPool<byte>.Shared.Return(array);
}
}
sealed class BitVector32BitStringConverter : PgBufferedConverter<BitVector32>
{
static int MaxSize => sizeof(int) + sizeof(int);
public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements)
{
bufferRequirements = BufferRequirements.Create(read: Size.CreateUpperBound(MaxSize), write: MaxSize);
return format is DataFormat.Binary;
}
protected override BitVector32 ReadCore(PgReader reader)
{
if (reader.CurrentRemaining > sizeof(int) + sizeof(int))
throw new InvalidCastException("Can't read a BIT(N) with more than 32 bits to BitVector32, only up to BIT(32).");
var bits = reader.ReadInt32();
return GetByteCountFromBitCount(bits) switch
{
4 => new(reader.ReadInt32()),
3 => new((reader.ReadInt16() << 8) + reader.ReadByte()),
2 => new(reader.ReadInt16() << 16),
1 => new(reader.ReadByte() << 24),
_ => new(0)
};
}
protected override void WriteCore(PgWriter writer, BitVector32 value)
{
writer.WriteInt32(32);
writer.WriteInt32(value.Data);
}
}
sealed class BoolBitStringConverter : PgBufferedConverter<bool>
{
static int MaxSize => sizeof(int) + sizeof(byte);
public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements)
{
bufferRequirements = BufferRequirements.Create(read: Size.CreateUpperBound(MaxSize), write: MaxSize);
return format is DataFormat.Binary;
}
protected override bool ReadCore(PgReader reader)
{
var bits = reader.ReadInt32();
return bits switch
{
> 1 => throw new InvalidCastException("Can't read a BIT(N) type to bool, only BIT(1)."),
// We make an accommodation for varbit with no data.
0 => false,
_ => (reader.ReadByte() & 128) is not 0
};
}
public override Size GetSize(SizeContext context, bool value, ref object? writeState) => MaxSize;
protected override void WriteCore(PgWriter writer, bool value)
{
writer.WriteInt32(1);
writer.WriteByte(value ? (byte)128 : (byte)0);
}
}
sealed class StringBitStringConverter : PgStreamingConverter<string>
{
public override string Read(PgReader reader)
=> Read(async: false, reader, CancellationToken.None).GetAwaiter().GetResult();
public override ValueTask<string> ReadAsync(PgReader reader, CancellationToken cancellationToken = default)
=> Read(async: true, reader, cancellationToken);
async ValueTask<string> Read(bool async, PgReader reader, CancellationToken cancellationToken)
{
if (reader.ShouldBuffer(sizeof(int)))
await reader.Buffer(async, sizeof(int), cancellationToken).ConfigureAwait(false);
var bits = reader.ReadInt32();
var bytes = new byte[GetByteCountFromBitCount(bits)];
if (async)
await reader.ReadBytesAsync(bytes, cancellationToken).ConfigureAwait(false);
else
reader.ReadBytes(bytes);
var bitArray = BitArrayBitStringConverter.ReadValue(bytes, bits);
var sb = new StringBuilder(bits);
for (var i = 0; i < bitArray.Count; i++)
sb.Append(bitArray[i] ? '1' : '0');
return sb.ToString();
}
public override Size GetSize(SizeContext context, string value, ref object? writeState)
{
if (value.AsSpan().IndexOfAnyExcept('0', '1') is not -1 and var index)
throw new ArgumentException($"Invalid bitstring character '{value[index]}' at index: {index}", nameof(value));
return sizeof(int) + GetByteCountFromBitCount(value.Length);
}
public override void Write(PgWriter writer, string value)
=> Write(async: false, writer, value, CancellationToken.None).GetAwaiter().GetResult();
public override ValueTask WriteAsync(PgWriter writer, string value, CancellationToken cancellationToken = default)
=> Write(async: true, writer, value, cancellationToken);
async ValueTask Write(bool async, PgWriter writer, string value, CancellationToken cancellationToken)
{
var byteCount = writer.Current.Size.Value - sizeof(int);
var array = ArrayPool<byte>.Shared.Rent(byteCount);
for (var pos = 0; pos < byteCount; pos++)
{
var bitPos = pos*8;
var bits = Math.Min(8, value.Length - bitPos);
var b = 0;
for (var i = 0; i < bits; i++)
b += (value[bitPos + i] == '1' ? 1 : 0) << (8 - i - 1);
array[pos] = (byte)b;
}
if (writer.ShouldFlush(sizeof(int)))
await writer.Flush(async, cancellationToken).ConfigureAwait(false);
writer.WriteInt32(value.Length);
if (async)
await writer.WriteBytesAsync(new ReadOnlyMemory<byte>(array, 0, byteCount), cancellationToken).ConfigureAwait(false);
else
writer.WriteBytes(new ReadOnlySpan<byte>(array, 0, byteCount));
ArrayPool<byte>.Shared.Return(array);
}
}
/// Note that for BIT(1), this resolver will return a bool by default, to align with SqlClient
/// (see discussion https://github.com/npgsql/npgsql/pull/362#issuecomment-59622101).
sealed class PolymorphicBitStringConverterResolver(PgTypeId bitString) : PolymorphicConverterResolver<object>(bitString)
{
BoolBitStringConverter? _boolConverter;
BitArrayBitStringConverter? _bitArrayConverter;
protected override PgConverter Get(Field? field)
=> field?.TypeModifier is 1
? _boolConverter ??= new BoolBitStringConverter()
: _bitArrayConverter ??= new BitArrayBitStringConverter();
}