-
Notifications
You must be signed in to change notification settings - Fork 874
Expand file tree
/
Copy pathNpgsqlLogSequenceNumber.cs
More file actions
335 lines (307 loc) · 17.5 KB
/
NpgsqlLogSequenceNumber.cs
File metadata and controls
335 lines (307 loc) · 17.5 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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
using System;
using System.Globalization;
// ReSharper disable once CheckNamespace
namespace NpgsqlTypes;
/// <summary>
/// Wraps a PostgreSQL Write-Ahead Log Sequence Number (see: https://www.postgresql.org/docs/current/datatype-pg-lsn.html)
/// </summary>
/// <remarks>
/// Log Sequence Numbers are a fundamental concept of the PostgreSQL Write-Ahead Log and by that of
/// PostgreSQL replication. See https://www.postgresql.org/docs/current/wal-internals.html for what they represent.
///
/// This struct provides conversions from/to <see cref="string"/> and <see cref="ulong"/> and beyond that tries to port
/// the methods and operators in https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/adt/pg_lsn.c
/// but nothing more.
/// </remarks>
public readonly struct NpgsqlLogSequenceNumber : IEquatable<NpgsqlLogSequenceNumber>, IComparable<NpgsqlLogSequenceNumber>
{
/// <summary>
/// Zero is used indicate an invalid Log Sequence Number. No XLOG record can begin at zero.
/// </summary>
public static readonly NpgsqlLogSequenceNumber Invalid = default;
readonly ulong _value;
/// <summary>
/// Initializes a new instance of <see cref="NpgsqlLogSequenceNumber"/>.
/// </summary>
/// <param name="value">The value to wrap.</param>
public NpgsqlLogSequenceNumber(ulong value)
=> _value = value;
/// <summary>
/// Returns a value indicating whether this instance is equal to a specified <see cref="NpgsqlLogSequenceNumber"/>
/// instance.
/// </summary>
/// <param name="other">A <see cref="NpgsqlLogSequenceNumber"/> instance to compare to this instance.</param>
/// <returns><see langword="true" /> if the current instance is equal to the value parameter;
/// otherwise, <see langword="false" />.</returns>
public bool Equals(NpgsqlLogSequenceNumber other)
=> _value == other._value;
/// <summary>
/// Compares this instance to a specified <see cref="NpgsqlLogSequenceNumber"/> and returns an indication of their
/// relative values.
/// </summary>
/// <param name="value">A <see cref="NpgsqlLogSequenceNumber"/> instance to compare to this instance.</param>
/// <returns>A signed number indicating the relative values of this instance and <paramref name="value" />.</returns>
public int CompareTo(NpgsqlLogSequenceNumber value)
=> _value.CompareTo(value._value);
/// <summary>
/// Returns a value indicating whether this instance is equal to a specified object.
/// </summary>
/// <param name="obj">An object to compare to this instance</param>
/// <returns><see langword="true" /> if the current instance is equal to the value parameter;
/// otherwise, <see langword="false" />.</returns>
public override bool Equals(object? obj)
=> obj is NpgsqlLogSequenceNumber lsn && lsn._value == _value;
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
=> _value.GetHashCode();
/// <summary>
/// Converts the numeric value of this instance to its equivalent string representation.
/// </summary>
/// <returns>The string representation of the value of this instance, consisting of two hexadecimal numbers of
/// up to 8 digits each, separated by a slash</returns>
public override string ToString()
=> unchecked($"{(uint)(_value >> 32):X}/{(uint)_value:X}");
/// <summary>
/// Converts the string representation of a Log Sequence Number to a <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="s">A string that represents the Log Sequence Number to convert.</param>
/// <returns>
/// A <see cref="NpgsqlLogSequenceNumber"/> equivalent to the Log Sequence Number specified in <paramref name="s" />.
/// </returns>
/// <exception cref="ArgumentNullException">The <paramref name="s" /> parameter is <see langword="null"/>.</exception>
/// <exception cref="OverflowException">
/// The <paramref name="s" /> parameter represents a number less than <see cref="ulong.MinValue"/> or greater than
/// <see cref="ulong.MaxValue"/>.
/// </exception>
/// <exception cref="FormatException">The <paramref name="s" /> parameter is not in the right format.</exception>
public static NpgsqlLogSequenceNumber Parse(string s)
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
=> s is null
? throw new ArgumentNullException(nameof(s))
: Parse(s.AsSpan());
/// <summary>
/// Converts the span representation of a Log Sequence Number to a <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="s">A span containing the characters that represent the Log Sequence Number to convert.</param>
/// <returns>
/// A <see cref="NpgsqlLogSequenceNumber"/> equivalent to the Log Sequence Number specified in <paramref name="s" />.
/// </returns>
/// <exception cref="OverflowException">
/// The <paramref name="s" /> parameter represents a number less than <see cref="ulong.MinValue"/> or greater than
/// <see cref="ulong.MaxValue"/>.
/// </exception>
/// <exception cref="FormatException">The <paramref name="s" /> parameter is not in the right format.</exception>
public static NpgsqlLogSequenceNumber Parse(ReadOnlySpan<char> s)
=> TryParse(s, out var parsed)
? parsed
: throw new FormatException($"Invalid Log Sequence Number: '{s.ToString()}'.");
/// <summary>
/// Tries to convert the string representation of a Log Sequence Number to an <see cref="NpgsqlLogSequenceNumber"/>
/// instance. A return value indicates whether the conversion succeeded or failed.
/// </summary>
/// <param name="s">A string that represents the Log Sequence Number to convert.</param>
/// <param name="result">
/// When this method returns, contains a <see cref="NpgsqlLogSequenceNumber"/> instance equivalent to the Log Sequence
/// Number contained in <paramref name="s"/>, if the conversion succeeded, or the default value for
/// <see cref="NpgsqlLogSequenceNumber"/> (<c>0</c>) if the conversion failed. The conversion fails if the <paramref name="s" />
/// parameter is <see langword="null"/> or <see cref="string.Empty"/>, is not in the right format, or represents a number
/// less than <see cref="ulong.MinValue"/> or greater than <see cref="ulong.MaxValue"/>. This parameter is
/// passed uninitialized; any value originally supplied in result will be overwritten.
/// </param>
/// <returns>
/// <see langword="true" /> if <paramref name="s"/>c> was converted successfully; otherwise, <see langword="false" />.
/// </returns>
public static bool TryParse(string s, out NpgsqlLogSequenceNumber result)
=> TryParse(s.AsSpan(), out result);
/// <summary>
/// Tries to convert the span representation of a Log Sequence Number to an <see cref="NpgsqlLogSequenceNumber"/>
/// instance. A return value indicates whether the conversion succeeded or failed.
/// </summary>
/// <param name="s">A span containing the characters that represent the Log Sequence Number to convert.</param>
/// <param name="result">
/// When this method returns, contains a <see cref="NpgsqlLogSequenceNumber"/> instance equivalent to the Log Sequence
/// Number contained in <paramref name="s"/>, if the conversion succeeded, or the default value for
/// <see cref="NpgsqlLogSequenceNumber"/> (<c>0</c>) if the conversion failed. The conversion fails if the <paramref name="s" />
/// parameter is empty, is not in the right format, or represents a number less than
/// <see cref="ulong.MinValue"/> or greater than <see cref="ulong.MaxValue"/>. This parameter is passed
/// uninitialized; any value originally supplied in result will be overwritten.
/// </param>
/// <returns>
/// <see langword="true" /> if <paramref name="s"/> was converted successfully; otherwise, <see langword="false" />.</returns>
public static bool TryParse(ReadOnlySpan<char> s, out NpgsqlLogSequenceNumber result)
{
for (var i = 0; i < s.Length; i++)
{
if (s[i] != '/') continue;
var firstPart = s.Slice(0, i);
var secondPart = s.Slice(++i);
if (!uint.TryParse(firstPart, NumberStyles.AllowHexSpecifier, null, out var first))
{
result = default;
return false;
}
if (!uint.TryParse(secondPart, NumberStyles.AllowHexSpecifier, null, out var second))
{
result = default;
return false;
}
result = new NpgsqlLogSequenceNumber(((ulong)first << 32) + second);
return true;
}
result = default;
return false;
}
/// <summary>
/// Converts the value of a 64-bit unsigned integer to a <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="value">A 64-bit unsigned integer.</param>
/// <returns>A new instance of <see cref="NpgsqlLogSequenceNumber"/> initialized to <paramref name="value" />.</returns>
public static explicit operator NpgsqlLogSequenceNumber(ulong value)
=> new(value);
/// <summary>
/// Converts the value of a <see cref="NpgsqlLogSequenceNumber"/> instance to a 64-bit unsigned integer value.
/// </summary>
/// <param name="value">A <see cref="NpgsqlLogSequenceNumber"/> instance</param>
/// <returns>The contents of <paramref name="value" /> as 64-bit unsigned integer.</returns>
public static explicit operator ulong(NpgsqlLogSequenceNumber value)
=> value._value;
/// <summary>
/// Returns a value that indicates whether two specified instances of <see cref="NpgsqlLogSequenceNumber"/> are equal.
/// </summary>
/// <param name="value1">The first Log Sequence Number to compare.</param>
/// <param name="value2">The second Log Sequence Number to compare.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value1" /> equals <paramref name="value2" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator ==(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value == value2._value;
/// <summary>
/// Returns a value that indicates whether two specified instances of <see cref="NpgsqlLogSequenceNumber"/> are not
/// equal.
/// </summary>
/// <param name="value1">The first Log Sequence Number to compare.</param>
/// <param name="value2">The second Log Sequence Number to compare.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value1" /> does not equal <paramref name="value2" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value != value2._value;
/// <summary>
/// Returns a value indicating whether a specified <see cref="NpgsqlLogSequenceNumber"/> instance is greater than
/// another specified <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The second value to compare.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value1" /> is greater than <paramref name="value2" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator >(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value > value2._value;
/// <summary>
/// Returns a value indicating whether a specified <see cref="NpgsqlLogSequenceNumber"/> instance is less than
/// another specified <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The second value to compare.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value1" /> is less than <paramref name="value2" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator <(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value < value2._value;
/// <summary>
/// Returns a value indicating whether a specified <see cref="NpgsqlLogSequenceNumber"/> instance is greater than or
/// equal to another specified <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The second value to compare.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value1" /> is greater than or equal to <paramref name="value2" />;
/// otherwise, <see langword="false" />.
/// </returns>
public static bool operator >=(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value >= value2._value;
/// <summary>
/// Returns the larger of two <see cref="NpgsqlLogSequenceNumber"/> values.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The second value to compare.</param>
/// <returns>
/// The larger of the two <see cref="NpgsqlLogSequenceNumber"/> values.
/// </returns>
public static NpgsqlLogSequenceNumber Larger(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value > value2._value ? value1 : value2;
/// <summary>
/// Returns the smaller of two <see cref="NpgsqlLogSequenceNumber"/> values.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The second value to compare.</param>
/// <returns>
/// The smaller of the two <see cref="NpgsqlLogSequenceNumber"/> values.
/// </returns>
public static NpgsqlLogSequenceNumber Smaller(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value < value2._value ? value1 : value2;
/// <summary>
/// Returns a value indicating whether a specified <see cref="NpgsqlLogSequenceNumber"/> instance is less than or
/// equal to another specified <see cref="NpgsqlLogSequenceNumber"/> instance.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The second value to compare.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value1" /> is less than or equal to <paramref name="value2" />;
/// otherwise, <see langword="false" />.
/// </returns>
public static bool operator <=(NpgsqlLogSequenceNumber value1, NpgsqlLogSequenceNumber value2)
=> value1._value <= value2._value;
/// <summary>
/// Subtracts two specified <see cref="NpgsqlLogSequenceNumber"/> values.
/// </summary>
/// <param name="first">The first <see cref="NpgsqlLogSequenceNumber"/> value.</param>
/// <param name="second">The second <see cref="NpgsqlLogSequenceNumber"/> value.</param>
/// <returns>The number of bytes separating those write-ahead log locations.</returns>
public static ulong operator -(NpgsqlLogSequenceNumber first, NpgsqlLogSequenceNumber second)
=> first._value < second._value
? second._value - first._value
: first._value - second._value;
/// <summary>
/// Subtract the number of bytes from a <see cref="NpgsqlLogSequenceNumber"/> instance, giving a new
/// <see cref="NpgsqlLogSequenceNumber"/> instance.
/// Handles both positive and negative numbers of bytes.
/// </summary>
/// <param name="lsn">
/// The <see cref="NpgsqlLogSequenceNumber"/> instance representing a write-ahead log location.
/// </param>
/// <param name="nbytes">The number of bytes to subtract.</param>
/// <returns>A new <see cref="NpgsqlLogSequenceNumber"/> instance.</returns>
/// <exception cref="OverflowException">
/// The resulting <see cref="NpgsqlLogSequenceNumber"/> instance would represent a number less than
/// <see cref="ulong.MinValue"/>.
/// </exception>
public static NpgsqlLogSequenceNumber operator -(NpgsqlLogSequenceNumber lsn, double nbytes)
=> double.IsNaN(nbytes) || double.IsInfinity(nbytes)
? throw new NotFiniteNumberException($"Cannot subtract {nbytes} from {nameof(NpgsqlLogSequenceNumber)}", nbytes)
: new NpgsqlLogSequenceNumber(checked((ulong)(lsn._value - nbytes)));
/// <summary>
/// Add the number of bytes to a <see cref="NpgsqlLogSequenceNumber"/> instance, giving a new
/// <see cref="NpgsqlLogSequenceNumber"/> instance.
/// Handles both positive and negative numbers of bytes.
/// </summary>
/// <param name="lsn">
/// The <see cref="NpgsqlLogSequenceNumber"/> instance representing a write-ahead log location.
/// </param>
/// <param name="nbytes">The number of bytes to add.</param>
/// <returns>A new <see cref="NpgsqlLogSequenceNumber"/> instance.</returns>
/// <exception cref="OverflowException">
/// The resulting <see cref="NpgsqlLogSequenceNumber"/> instance would represent a number greater than
/// <see cref="ulong.MaxValue"/>.
/// </exception>
public static NpgsqlLogSequenceNumber operator +(NpgsqlLogSequenceNumber lsn, double nbytes)
=> double.IsNaN(nbytes) || double.IsInfinity(nbytes)
? throw new NotFiniteNumberException($"Cannot add {nbytes} to {nameof(NpgsqlLogSequenceNumber)}", nbytes)
: new NpgsqlLogSequenceNumber(checked((ulong)(lsn._value + nbytes)));
}