-
Notifications
You must be signed in to change notification settings - Fork 890
Expand file tree
/
Copy pathBitStringConverters.cs
More file actions
244 lines (203 loc) · 10 KB
/
Copy pathBitStringConverters.cs
File metadata and controls
244 lines (203 loc) · 10 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
240
241
242
243
244
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);
}
protected override Size BindValue(in BindContext 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 ConverterDescriptor GetDescriptor(in DescriptorContext context)
=> ConverterDescriptor.Invariant with { BufferRequirements = BufferRequirements.Create(read: Size.CreateUpperBound(MaxSize), write: MaxSize) };
public override BitVector32 Read(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)
};
}
public override void Write(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 ConverterDescriptor GetDescriptor(in DescriptorContext context)
=> ConverterDescriptor.Invariant with { BufferRequirements = BufferRequirements.Create(read: Size.CreateUpperBound(MaxSize), write: MaxSize) };
public override bool Read(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
};
}
protected override Size BindValue(in BindContext context, bool value, ref object? writeState) => MaxSize;
public override void Write(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();
}
protected override Size BindValue(in BindContext 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);
}
}
/// For BIT(1) columns specifically (read from a field with TypeModifier == 1), this provider returns a bool converter
/// to align with SqlClient (see discussion https://github.com/npgsql/npgsql/pull/362#issuecomment-59622101).
/// Otherwise we return a BitArray converter. Polymorphic writing through this provider is not supported.
sealed class PolymorphicBitStringTypeInfoProvider(PgSerializerOptions options, PgTypeId bitString) : PgConcreteTypeInfoProvider<object>
{
readonly PgConcreteTypeInfo _boolConcreteTypeInfo = PgConcreteTypeInfo.Create(options, new BoolBitStringConverter(), bitString, supportsWriting: false);
readonly PgConcreteTypeInfo _bitArrayConcreteTypeInfo = PgConcreteTypeInfo.Create(options, new BitArrayBitStringConverter(), bitString, supportsWriting: false);
// Dispatched concretes vary in advertised Type (bool vs BitArray) per field's TypeModifier.
internal override bool AllowConcreteVariance => true;
protected override PgConcreteTypeInfo GetDefaultCore(PgTypeId? pgTypeId)
=> GetConcreteInfo(typeModifier: -1);
protected override PgConcreteTypeInfo? GetForValueCore(in ProviderValueContext context, object? value, ref object? writeState)
=> throw new NotSupportedException("Polymorphic writing is not supported.");
protected override PgConcreteTypeInfo GetForFieldCore(in ProviderFieldContext context)
=> GetConcreteInfo(context.TypeModifier);
PgConcreteTypeInfo GetConcreteInfo(int typeModifier)
=> typeModifier is 1 ? _boolConcreteTypeInfo : _bitArrayConcreteTypeInfo;
}