Shared.cs (63793B)
1 using FNVHash; 2 using Microsoft.Data.SqlClient; 3 using Std; 4 using Std.Clone; 5 using Std.Cmp; 6 using Std.Collections.HashMap; 7 using Std.Convert; 8 using Std.Error; 9 using Std.Hashing; 10 using Std.Iter; 11 using Std.Maybe; 12 using Std.Num; 13 using Std.Ops; 14 using Std.Result; 15 using Std.Vec; 16 using System; 17 using System.Data; 18 using System.Data.SqlTypes; 19 using System.Diagnostics; 20 using System.Runtime.CompilerServices; 21 using System.Runtime.InteropServices; 22 using System.Text; 23 #region Namespaces 24 namespace SQLServer { 25 #region Types 26 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 0)] 27 readonly struct ActualType: IClone<ActualType>, IHashable, IInto<ActualType>, IInto<string>, IEq<ActualType> { 28 29 #region Type-level Constructors 30 #endregion 31 32 #region Instance Constructors 33 public ActualType() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 34 internal ActualType(Type type) => Value = type; 35 #endregion 36 37 #region Type-level Fields 38 #endregion 39 40 #region Instance Fields 41 [FieldOffset(0)] internal readonly Type Value; 42 #endregion 43 44 #region Type-level Properties 45 #endregion 46 47 #region Instance Properties 48 #endregion 49 50 #region Type-level Functions 51 #endregion 52 53 #region Instance Functions 54 public readonly ActualType Clone() => this; 55 public override readonly bool Equals(object? _) => false; 56 public override readonly int GetHashCode() => 0; 57 public readonly Unit Hash<THasher>(ref THasher hasher) where THasher: notnull, IHasher => hasher.WriteInt(Value.GetHashCode()); 58 public readonly ActualType Into() => this; 59 public readonly string IntoString() => ToString(); 60 readonly string IInto<string>.Into() => IntoString(); 61 public override readonly string ToString() => Value.Name; 62 readonly Result<ActualType, Bottom> ITryInto<ActualType, Bottom>.TryInto() => new(this); 63 readonly Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 64 #endregion 65 66 #region Operators 67 public static bool operator !=(ActualType val0, ActualType val1) => !(val0 == val1); 68 public static bool operator ==(ActualType val0, ActualType val1) => val0.Value == val1.Value; 69 public static implicit operator ActualType(Type val) => new(val); 70 public static implicit operator Type(ActualType val) => val.Value; 71 #endregion 72 73 #region Types 74 #endregion 75 } 76 // This is a fairly large type and it will likely be used frequently as the error in a Result. 77 // If this were a struct, the Result would be large. If it were the case the Result 78 // would be local only, then it would be better if this were a struct; however it's likely that the Result will simply 79 // be passed down the call stack making it important the copies are small so we make it a sealed class instead. 80 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8, Size = 40)] 81 public sealed class BulkRowError: ISum<Prod<IError, string>, Prod<IError, byte[]>, Prod<IError, string, byte[]>, Prod<StackTrace, string>, Prod<StackTrace, byte[]>, Prod<StackTrace, string, byte[]>, Prod<string, string, string, int>, Prod<byte[], string, string, int>, Prod<string, byte[], string, string, int>>, IBulkRowError, IInto<BulkRowError> { 82 83 #region Type-level Constructors 84 #endregion 85 86 #region Instance Constructors 87 BulkRowError() => (_err, _trace, _memberName, _message, _data, _sourceFilePath, _sourceLineNumber, Var) = (default!, default!, default!, default!, default!, default!, default, default); 88 BulkRowError(IError err, string message) : this() => (Var, _err, _message) = (Tag.ErrorMessage, err, message); 89 BulkRowError(IError err, byte[] data) : this() => (Var, _err, _data) = (Tag.ErrorData, err, data); 90 BulkRowError(IError err, string message, byte[] data) : this() => (Var, _err, _message, _data) = (Tag.ErrorMessageAndData, err, message, data); 91 BulkRowError(StackTrace trace, string message) : this() => (Var, _trace, _message) = (Tag.TraceMessage, trace, message); 92 BulkRowError(StackTrace trace, byte[] data) : this() => (Var, _trace, _data) = (Tag.TraceData, trace, data); 93 BulkRowError(StackTrace trace, string message, byte[] data) : this() => (Var, _trace, _message, _data) = (Tag.TraceMessageAndData, trace, message, data); 94 BulkRowError(string message, string memberName, string sourceFilePath, int sourceLineNumber) : this() => (Var, _message, _memberName, _sourceFilePath, _sourceLineNumber) = (Tag.MessageAndCallerInfo, message, memberName, sourceFilePath, sourceLineNumber); 95 BulkRowError(byte[] data, string memberName, string sourceFilePath, int sourceLineNumber) : this() => (Var, _data, _memberName, _sourceFilePath, _sourceLineNumber) = (Tag.DataAndCallerInfo, data, memberName, sourceFilePath, sourceLineNumber); 96 BulkRowError(string message, byte[] data, string memberName, string sourceFilePath, int sourceLineNumber) : this() => (Var, _message, _data, _memberName, _sourceFilePath, _sourceLineNumber) = (Tag.MessageAndDataAndCallerInfo, message, data, memberName, sourceFilePath, sourceLineNumber); 97 #endregion 98 99 #region Type-level Fields 100 #endregion 101 102 #region Instance Fields 103 [FieldOffset(0)] readonly IError _err; 104 [FieldOffset(0)] readonly StackTrace _trace; 105 [FieldOffset(0)] readonly string _memberName; 106 [FieldOffset(8)] readonly string _message; 107 [FieldOffset(16)] readonly byte[] _data; 108 [FieldOffset(24)] readonly string _sourceFilePath; 109 [FieldOffset(32)] readonly int _sourceLineNumber; 110 [FieldOffset(36)] public readonly Tag Var; 111 #endregion 112 113 #region Type-level Properties 114 #endregion 115 116 #region Instance Properties 117 public Var9 Variant => (Var9)Var; 118 public Prod<IError, string> Variant0 => IntoErrorMessage(); 119 public Prod<IError, byte[]> Variant1 => IntoErrorData(); 120 public Prod<IError, string, byte[]> Variant2 => IntoErrorMessageAndData(); 121 public Prod<StackTrace, string> Variant3 => IntoTraceMessage(); 122 public Prod<StackTrace, byte[]> Variant4 => IntoTraceData(); 123 public Prod<StackTrace, string, byte[]> Variant5 => IntoTraceMessageAndData(); 124 public Prod<string, string, string, int> Variant6 => IntoMessageAndCallerInfo(); 125 public Prod<byte[], string, string, int> Variant7 => IntoDataAndCallerInfo(); 126 public Prod<string, byte[], string, string, int> Variant8 => IntoMessageAndDataAndCallerInfo(); 127 public nvarchar Trace => Var switch { 128 Tag.TraceMessage or Tag.TraceData or Tag.TraceMessageAndData => nvarchar.New(_trace.ToString()), 129 Tag.DataAndCallerInfo or Tag.MessageAndCallerInfo or Tag.MessageAndDataAndCallerInfo => nvarchar.New($@"CallerMemberName: {_memberName} 130 CallerFilePath: {_sourceFilePath} 131 CallerLineNumber: {_sourceLineNumber.ToString()}"), 132 _ => nvarchar.NULL, 133 }; 134 public nvarchar Message => Var switch { 135 Tag.ErrorMessage or Tag.ErrorMessageAndData or Tag.TraceMessage or Tag.TraceMessageAndData or Tag.MessageAndCallerInfo or Tag.MessageAndDataAndCallerInfo => nvarchar.New(_message), 136 _ => nvarchar.NULL, 137 }; 138 public varbinary Data => Var switch { 139 Tag.ErrorData or Tag.ErrorMessageAndData or Tag.TraceData or Tag.TraceMessageAndData or Tag.DataAndCallerInfo or Tag.MessageAndDataAndCallerInfo => varbinary.New(_data), 140 _ => varbinary.NULL, 141 }; 142 #endregion 143 144 #region Type-level Functions 145 public static BulkRowError ErrorMessage(IError err, string message) => new(err, message); 146 public static BulkRowError ErrorData(IError err, byte[] data) => new(err, data); 147 public static BulkRowError ErrorMessageAndData(IError err, string message, byte[] data) => new(err, message, data); 148 public static BulkRowError TraceMessage(StackTrace trace, string message) => new(trace, message); 149 public static BulkRowError TraceData(StackTrace trace, byte[] data) => new(trace, data); 150 public static BulkRowError TraceMessageAndData(StackTrace trace, string message, byte[] data) => new(trace, message, data); 151 public static BulkRowError MessageAndCallerInfo(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) => new(message, memberName, sourceFilePath, sourceLineNumber); 152 public static BulkRowError DataAndCallerInfo(byte[] data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) => new(data, memberName, sourceFilePath, sourceLineNumber); 153 public static BulkRowError MessageAndDataAndCallerInfo(string message, byte[] data, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) => new(message, data, memberName, sourceFilePath, sourceLineNumber); 154 #endregion 155 156 #region Instance Functions 157 public sealed override bool Equals(object? _) => false; 158 public sealed override int GetHashCode() => 0; 159 public BulkRowError Into() => this; 160 public string IntoString() => ToString(); 161 string IInto<string>.Into() => IntoString(); 162 public Prod<IError, string> IntoErrorMessage() => Var == Tag.ErrorMessage ? new(_err, _message) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not ErrorMessage!"); 163 public Prod<IError, byte[]> IntoErrorData() => Var == Tag.ErrorData ? new(_err, _data) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not ErrorData!"); 164 public Prod<IError, string, byte[]> IntoErrorMessageAndData() => Var == Tag.ErrorMessageAndData ? new(_err, _message, _data) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not ErrorMessageAndData!"); 165 public Prod<StackTrace, string> IntoTraceMessage() => Var == Tag.TraceMessage ? new(_trace, _message) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not TraceMessage!"); 166 public Prod<StackTrace, byte[]> IntoTraceData() => Var == Tag.TraceData ? new(_trace, _data) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not TraceData!"); 167 public Prod<StackTrace, string, byte[]> IntoTraceMessageAndData() => Var == Tag.TraceMessageAndData ? new(_trace, _message, _data) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not TraceMessageAndData!"); 168 public Prod<string, string, string, int> IntoMessageAndCallerInfo() => Var == Tag.MessageAndCallerInfo ? new(_message, _memberName, _sourceFilePath, _sourceLineNumber) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not MessageAndCallerInfo!"); 169 public Prod<byte[], string, string, int> IntoDataAndCallerInfo() => Var == Tag.DataAndCallerInfo ? new(_data, _memberName, _sourceFilePath, _sourceLineNumber) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not DataAndCallerInfo!"); 170 public Prod<string, byte[], string, string, int> IntoMessageAndDataAndCallerInfo() => Var == Tag.MessageAndDataAndCallerInfo ? new(_message, _data, _memberName, _sourceFilePath, _sourceLineNumber) : throw new InvalidOperationException($"The BulkRowError variant {Var.ToString()} is not MessageAndDataAndCallerInfo!"); 171 public Maybe<IError> Source() => Var switch { 172 Tag.ErrorMessage or Tag.ErrorData or Tag.ErrorMessageAndData => new(_err), 173 _ => Maybe<IError>.None(), 174 }; 175 public Maybe<StackTrace> StackTrace() => Var switch { 176 Tag.TraceMessage or Tag.TraceData or Tag.TraceMessageAndData => new(_trace), 177 _ => Maybe<StackTrace>.None(), 178 }; 179 [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "Need to make sure code handles all enum values.")] 180 public sealed override string ToString() => $@"{Var.ToString()}({Var switch { 181 Tag.ErrorMessage => $@"IError: {_err.Into()}, 182 Message: {_message}", 183 Tag.ErrorData => $@"IError: {_err.Into()}, 184 Data: {_data.AsSpan().UpperHex()}", 185 Tag.ErrorMessageAndData => $@"IError: {_err.Into()}, 186 Message: {_message}, 187 Data: {_data.AsSpan().UpperHex()}", 188 Tag.TraceMessage => $@"StackTrace: {_trace.ToString()}, 189 Message: {_message}", 190 Tag.TraceData => $@"StackTrace: {_trace.ToString()}, 191 Data: {_data.AsSpan().UpperHex()}", 192 Tag.TraceMessageAndData => $@"StackTrace: {_trace.ToString()}, 193 Message: {_message}, 194 Data: {_data.AsSpan().UpperHex()}", 195 Tag.MessageAndCallerInfo => $@"Message: {_message}, 196 CallerMemberName: {_memberName} 197 CallerFilePath: {_sourceFilePath} 198 CallerLineNumber: {_sourceLineNumber.ToString()}", 199 Tag.DataAndCallerInfo => $@"Data: {_data.AsSpan().UpperHex()}, 200 CallerMemberName: {_memberName} 201 CallerFilePath: {_sourceFilePath} 202 CallerLineNumber: {_sourceLineNumber.ToString()}", 203 Tag.MessageAndDataAndCallerInfo => $@"Message: {_message}, 204 Data: {_data.AsSpan().UpperHex()}, 205 CallerMemberName: {_memberName} 206 CallerFilePath: {_sourceFilePath} 207 CallerLineNumber: {_sourceLineNumber.ToString()}", 208 _ => throw new InvalidOperationException($"{Var.ToString()} is an invalid BulkRowError variant!"), 209 }})"; 210 Result<BulkRowError, Bottom> ITryInto<BulkRowError, Bottom>.TryInto() => new(this); 211 Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 212 #endregion 213 214 #region Operators 215 #endregion 216 217 #region Types 218 public enum Tag: uint { 219 ErrorMessage = uint.MinValue, 220 ErrorData = 1u, 221 ErrorMessageAndData = 2u, 222 TraceMessage = 3u, 223 TraceData = 4u, 224 TraceMessageAndData = 5u, 225 MessageAndCallerInfo = 6u, 226 DataAndCallerInfo = 7u, 227 MessageAndDataAndCallerInfo = 8u, 228 } 229 #endregion 230 } 231 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8, Size = 16)] 232 public readonly struct BulkWriterCreateError: ISum<StackTrace, StackTrace, StackTrace, StackTrace, StackTrace>, IError, IInto<BulkWriterCreateError> { 233 234 #region Type-level Constructors 235 #endregion 236 237 #region Instance Constructors 238 public BulkWriterCreateError() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 239 internal BulkWriterCreateError(Tag flag, StackTrace trace) => (Var, Backtrace) = (flag, trace); 240 #endregion 241 242 #region Type-level Fields 243 #endregion 244 245 #region Instance Fields 246 [FieldOffset(0)] public readonly Tag Var; 247 [FieldOffset(8)] public readonly StackTrace Backtrace; 248 #endregion 249 250 #region Type-level Properties 251 #endregion 252 253 #region Instance Properties 254 public readonly Var5 Variant => (Var5)Var; 255 public readonly StackTrace Variant0 => Var == Tag.DatabaseIsReadOnly ? Backtrace : throw new InvalidOperationException($"The BulkWriterCreateError variant, {Var.ToString()}, is not DatabaseIsReadOnly!"); 256 public readonly StackTrace Variant1 => Var == Tag.TypeMismatch ? Backtrace : throw new InvalidOperationException($"The BulkWriterCreateError variant, {Var.ToString()}, is not TypeMismatch!"); 257 public readonly StackTrace Variant2 => Var == Tag.InvalidErrorRatio ? Backtrace : throw new InvalidOperationException($"The BulkWriterCreateError variant, {Var.ToString()}, is not InvalidErrorRatio!"); 258 public readonly StackTrace Variant3 => Var == Tag.ProcessNameLengthExceeds128 ? Backtrace : throw new InvalidOperationException($"The BulkWriterCreateError variant, {Var.ToString()}, is not ProcessNameLengthExceeds128!"); 259 public readonly StackTrace Variant4 => Var == Tag.UserNameLengthExceeds128 ? Backtrace : throw new InvalidOperationException($"The BulkWriterCreateError variant, {Var.ToString()}, is not UserNameLengthExceeds128!"); 260 #endregion 261 262 #region Type-level Functions 263 #endregion 264 265 #region Instance Functions 266 public override readonly bool Equals(object? _) => false; 267 public override readonly int GetHashCode() => 0; 268 public readonly BulkWriterCreateError Into() => this; 269 public readonly string IntoString() => ToString(); 270 readonly string IInto<string>.Into() => IntoString(); 271 public readonly Maybe<IError> Source() => Maybe<IError>.None(); 272 public readonly Maybe<StackTrace> StackTrace() => new(Backtrace); 273 public override readonly string ToString() => $"{Var.ToString()}({Backtrace.ToString()})"; 274 readonly Result<BulkWriterCreateError, Bottom> ITryInto<BulkWriterCreateError, Bottom>.TryInto() => new(this); 275 readonly Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 276 #endregion 277 278 #region Operators 279 #endregion 280 281 #region Types 282 public enum Tag: ulong { 283 DatabaseIsReadOnly = ulong.MinValue, 284 TypeMismatch = 1ul, 285 InvalidErrorRatio = 2ul, 286 ProcessNameLengthExceeds128 = 3ul, 287 UserNameLengthExceeds128 = 4ul, 288 } 289 #endregion 290 } 291 sealed class ErrorReader: IDataReader { 292 293 #region Type-level Constructors 294 #endregion 295 296 #region Instance Constructors 297 ErrorReader() => (Table, _vec, _index, _current, ProcessName, User, _id, IsClosed) = (default, default, default, default, default!, default!, default, default); 298 // MUST ensure that processName and userName are no more than 128 chars in length before calling! 299 internal ErrorReader(ErrorTable table, Vec<Prod<nvarchar, nvarchar, varbinary>> vec, string processName, string userName) => (Table, _vec, _index, _current, ProcessName, User, _id, IsClosed) = (table, vec, uint.MinValue, default, processName, userName, short.MinValue, false); 300 #endregion 301 302 #region Type-level Fields 303 static readonly Type[] _dataTypes = new Type[7] { typeof(string), typeof(DateTimeOffset), typeof(short), typeof(string), typeof(string), typeof(string), typeof(byte[]) }; 304 const string _error = "The error table must conform to the following schema: <name0> nvarchar(128) NOT NULL, <name1> datetimeoffset(7) NOT NULL, <name2> smallint NOT NULL, <name3> nvarchar(max) NOT NULL, <name4> nvarchar(max) NULL, <name5> nvarchar(max) NULL, <name6> varbinary(max) NULL."; 305 #endregion 306 307 #region Instance Fields 308 internal readonly string ProcessName; 309 internal readonly string User; 310 short _id; 311 internal readonly ErrorTable Table; 312 Prod<nvarchar, nvarchar, varbinary> _current; 313 Vec<Prod<nvarchar, nvarchar, varbinary>> _vec; 314 uint _index; 315 #endregion 316 317 #region Type-level Properties 318 #endregion 319 320 #region Instance Properties 321 public bool IsClosed { get; private set; } 322 public int Depth => 0; 323 public int FieldCount => 7; 324 public object this[int ordinal] => GetValue(ordinal); 325 public object this[string columnName] => GetValue(GetOrdinal(columnName)); 326 public int RecordsAffected => -1; 327 #endregion 328 329 #region Type-level Functions 330 #endregion 331 332 #region Instance Functions 333 public void Close() => Dispose(); 334 public void Dispose() { 335 336 if (IsClosed) { return; } 337 _index = _vec.Len; 338 _current = default; 339 IsClosed = true; 340 } 341 public sealed override bool Equals(object? _) => false; 342 public bool GetBoolean(int _) => throw new InvalidOperationException(_error); 343 public byte GetByte(int ordinal) => (byte)GetValue(ordinal); 344 public long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) { 345 346 var val = GetValue(ordinal); 347 var bytes = (byte[])val; 348 var offset = (int)dataOffset; 349 var len = bytes.Length - offset; 350 if (len <= 0) { return 0L; } 351 var count = Math.Min(len, length); 352 var i = 0; 353 while (i < count) { buffer![bufferOffset + i] = bytes[offset + i++]; } 354 return count; 355 } 356 public char GetChar(int ordinal) { 357 358 var val = (string)GetValue(ordinal); 359 return val.Length == 1 ? val[0] : throw new InvalidCastException(); 360 } 361 public long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) { 362 363 var val = GetValue(ordinal); 364 var chars = (string)val; 365 var offset = (int)dataOffset; 366 var len = chars.Length - offset; 367 if (len <= 0) { return 0L; } 368 var count = Math.Min(len, length); 369 var i = 0; 370 while (i < count) { buffer![bufferOffset + i] = chars[offset + i++]; } 371 return count; 372 } 373 public IDataReader GetData(int _) => throw new NotSupportedException(); 374 public string GetDataTypeName(int ordinal) => GetFieldType(ordinal).Name; 375 public DateTime GetDateTime(int ordinal) => ((DateTimeOffset)GetValue(ordinal)).DateTime; 376 public decimal GetDecimal(int _) => throw new InvalidOperationException(_error); 377 public double GetDouble(int _) => throw new InvalidOperationException(_error); 378 public Type GetFieldType(int ordinal) => _dataTypes[ordinal]; 379 public float GetFloat(int _) => throw new InvalidOperationException(_error); 380 public Guid GetGuid(int _) => throw new InvalidOperationException(_error); 381 public sealed override int GetHashCode() => 0; 382 public short GetInt16(int ordinal) => (short)GetValue(ordinal); 383 public int GetInt32(int _) => throw new InvalidOperationException(_error); 384 public long GetInt64(int _) => throw new InvalidOperationException(_error); 385 public string GetName(int ordinal) => Table.Value[(ushort)ordinal].Name; 386 public int GetOrdinal(string name) { 387 388 for (ushort i = 0; i < Table.Value.ColumnCount; i++) { if (Table.Value.Schema.Name.Culture.CompareInfo.Compare(name, Table.Value[i].Name, Table.Value.Schema.Name.Options) == 0) { return i; } } 389 throw new ArgumentException($"The column name, {name}, does not exist in {Table.IntoString()}."); 390 } 391 public DataTable GetSchemaTable() { 392 393 DataTable schema = new() { MinimumCapacity = 7, TableName = $"{Table.Value.Schema.Name.Value}.{Table.Value.Name}", Locale = Table.Value.Schema.Name.Culture }; 394 _ = schema.Columns.Add("Ordinal", typeof(ushort)); 395 _ = schema.Columns.Add("ColumnName", typeof(string)); 396 _ = schema.Columns.Add("DataType", typeof(Type)); 397 for (ushort i = 0; i < Table.Value.ColumnCount; i++) { _ = schema.Rows.Add(i, Table.Value[i].Name, GetFieldType(i)); } 398 return schema; 399 } 400 public string GetString(int ordinal) => (string)GetValue(ordinal); 401 public object GetValue(int ordinal) { 402 403 short id = 0; 404 405 if (ordinal == 2) { 406 id = _id; 407 // We don't want to cause exceptions, so we use wrapping addition. 408 // Note that it is EXTREMELY unlikely wrapping behavior will ever occur especially since the 409 // bulk writers are the only types that can create this type, and they create a new ErrorReader 410 // every 4096 errors. 411 _id = _id.WrappingAdd(1); 412 } 413 return ordinal switch { 414 0 => ProcessName!, 415 1 => DateTimeOffset.Now, 416 2 => id, 417 3 => User, 418 4 => _current.Item0.Value!, 419 5 => _current.Item1.Value!, 420 6 => _current.Item2.Value!, 421 _ => throw new ArgumentException($"The ordinal position, {ordinal.ToString()}, is not inclusively between 0 and 6."), 422 }; 423 } 424 public int GetValues(object[] values) { 425 426 values[0] = GetValue(0); 427 values[1] = GetValue(1); 428 values[2] = GetValue(2); 429 values[3] = GetValue(3); 430 values[4] = GetValue(4); 431 values[5] = GetValue(5); 432 values[6] = GetValue(6); 433 return 7; 434 } 435 public bool IsDBNull(int ordinal) => ordinal switch { 4 => _current.Item0.IsNULL, 5 => _current.Item1.IsNULL, 6 => _current.Item2.IsNULL, _ => false, }; 436 public bool NextResult() { 437 438 _index = _vec.Len; 439 _current = default; 440 return false; 441 } 442 public bool Read() { 443 if (_index < _vec.Len) { 444 _current = _vec[_index++]; 445 return true; 446 } else { 447 return false; 448 } 449 } 450 public sealed override string ToString() => string.Empty; 451 #endregion 452 453 #region Operators 454 #endregion 455 456 #region Types 457 #endregion 458 } 459 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 0)] 460 public readonly struct ErrorTable: IClone<ErrorTable>, IEq<ErrorTable>, IHashable, IInto<ErrorTable>, IInto<string> { 461 462 #region Type-level Constructors 463 #endregion 464 465 #region Instance Constructors 466 public ErrorTable() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 467 ErrorTable(UserTable name) => (Value, ContainsEncrypted) = (name, name.ContainsEncryptedColumn()); 468 #endregion 469 470 #region Type-level Fields 471 static readonly Fn<ushort, bool> _lenIs128 = (x) => x == 128u; 472 static readonly Fn<Result<ErrorTable, InvalidErrorTable>> _invalidErrTable = () => new(new InvalidErrorTable(InvalidErrorTable.Tag.Does_not_contain_non_unique_clustered_index_whose_index_keys_are_the_first_three_columns, new StackTrace(1, true))); 473 #endregion 474 475 #region Instance Fields 476 public readonly bool ContainsEncrypted; 477 public readonly UserTable Value; 478 #endregion 479 480 #region Type-level Properties 481 #endregion 482 483 #region Instance Properties 484 #endregion 485 486 #region Type-level Functions 487 // An error table must conform to the following schema: 488 // <name0> nvarchar(128) NOT NULL, 489 // <name1> datetimeoffset(7) NOT NULL, 490 // <name2> smallint NOT NULL, 491 // <name3> nvarchar(128) NOT NULL, 492 // <name4> nvarchar(max) NULL, 493 // <name5> nvarchar(max) NULL, 494 // <name6> varbinary(max) NULL 495 // Additionally must not contain any non-clustered indexes but must contain a non-unique clustered index 496 // whose index columns are <name0> and <name1> in order. Must not contain any child objects (e.g., constraints) either. 497 // Obviously must not be in a ReadOnly database. 498 // The intent of <name0> is to be a quick description or source of the error (e.g., process name). 499 // The intent of <name1> is to be the date and time the error occurred. 500 // The intent of <name2> is to be an ID for rows that have the same values for <name0> and <name1>. 501 // The intent of <name3> is to be the name of the user the process that generated the error ran under. 502 // The intent of <name4> is to be detailed information of where the error occurred (e.g., a stack trace). 503 // The intent of <name5> is to be a message containing the error that occurred and/or the raw data that caused the error. 504 // The intent of <name6> is to be a the raw data that caused the error (assuming <name4> cannot be used). 505 [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1502:Rewrite or refactor the code to decrease its complexity below '26'.", Justification = "No real alternative.")] 506 public static Result<ErrorTable, InvalidErrorTable> New(in UserTable errorTable) { 507 508 if (errorTable.Schema.Database.IsReadOnly) { 509 return new(new InvalidErrorTable(InvalidErrorTable.Tag.Database_is_readonly, new StackTrace(1, true))); 510 } else if(errorTable.ColumnCount != 7) { 511 return new(new InvalidErrorTable(InvalidErrorTable.Tag.Does_not_contain_7_columns, new StackTrace(1, true))); 512 } else { 513 ref readonly var col = ref errorTable[ushort.MinValue]; 514 if (!(!col.IsNullable && col.ComputedInfo.IsNone && col.EncryptionInfo.IsNone && col.MaxLength.MapOr(false, _lenIs128) && col.DataType == typeof(nvarchar))) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.First_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128, new StackTrace(1, true))); } 515 col = ref errorTable[1]; 516 if (!(!col.IsNullable && col.ComputedInfo.IsNone && col.EncryptionInfo.IsNone && col.DataType == typeof(datetimeoffset) && col.Scale.Unwrap() == 7)) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.Second_column_not_a_non_nullable_non_encrypted_non_computed_datetimeoffset_7, new StackTrace(1, true))); } 517 col = ref errorTable[2]; 518 if (!(!col.IsNullable && col.ComputedInfo.IsNone && col.EncryptionInfo.IsNone && col.DataType == typeof(smallint))) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.Third_column_not_a_non_nullable_non_encrypted_non_computed_smallint, new StackTrace(1, true))); } 519 col = ref errorTable[3]; 520 if (!(!col.IsNullable && col.ComputedInfo.IsNone && col.EncryptionInfo.IsNone && col.MaxLength.MapOr(false, _lenIs128) && col.DataType == typeof(nvarchar))) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.Fourth_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128, new StackTrace(1, true))); } 521 col = ref errorTable[4]; 522 if (!(col.IsNullable && col.ComputedInfo.IsNone && col.EncryptionInfo.IsNone && col.MaxLength.IsNone && col.DataType == typeof(nvarchar))) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.Fifth_column_not_a_nullable_non_encrypted_non_computed_nvarchar_max, new StackTrace(1, true))); } 523 col = ref errorTable[5]; 524 if (!(col.IsNullable && col.ComputedInfo.IsNone && col.MaxLength.IsNone && col.DataType == typeof(nvarchar))) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.Sixth_column_not_a_nullable_non_computed_nvarchar_max, new StackTrace(1, true))); } 525 col = ref errorTable[6]; 526 if (!(col.IsNullable && col.ComputedInfo.IsNone && col.MaxLength.IsNone && col.DataType == typeof(varbinary))) { return new(new InvalidErrorTable(InvalidErrorTable.Tag.Seventh_column_not_a_nullable_non_computed_varbinary_max, new StackTrace(1, true))); } 527 528 if (errorTable.ChildObjectCount != ushort.MinValue) { 529 return new(new InvalidErrorTable(InvalidErrorTable.Tag.Contains_child_objects, new StackTrace(1, true))); 530 } else { 531 var errorCopy = errorTable; 532 return errorTable.GetClusteredIndex().AndThen( 533 (ix) => !(ix.ColumnCount == 3u && !ix.IsUnique && ix[ushort.MinValue].Item0 == errorCopy[ushort.MinValue] && ix[1].Item0 == errorCopy[1] && ix[2].Item0 == errorCopy[2]) ? Maybe<Index>.None() : new(ix) 534 ).MapOrElse( 535 _invalidErrTable, 536 (_) => errorCopy.IndexCount != 1 ? new(new InvalidErrorTable(InvalidErrorTable.Tag.Contains_non_clustered_indexes, new StackTrace(1, true))) : new(new ErrorTable(errorCopy)) 537 ); 538 } 539 } 540 } 541 #endregion 542 543 #region Instance Functions 544 public readonly ErrorTable Clone() => this; 545 public override readonly bool Equals(object? _) => false; 546 public override readonly int GetHashCode() => 0; 547 public readonly Unit Hash<THasher>(ref THasher hasher) where THasher: notnull, IHasher => Value.Hash(ref hasher); 548 public readonly ErrorTable Into() => this; 549 public readonly string IntoString() => ToString(); 550 readonly string IInto<string>.Into() => IntoString(); 551 public override readonly string ToString() => Value.IntoString(); 552 readonly Result<ErrorTable, Bottom> ITryInto<ErrorTable, Bottom>.TryInto() => new(this); 553 readonly Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 554 #endregion 555 556 #region Operators 557 public static bool operator !=(ErrorTable val0, ErrorTable val1) => !(val0 == val1); 558 public static bool operator ==(ErrorTable val0, ErrorTable val1) => val0.Value == val1.Value; 559 #endregion 560 561 #region Types 562 #endregion 563 } 564 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8, Size = 16)] 565 public readonly struct InvalidErrorTable: ISum<StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace>, IError, IInto<InvalidErrorTable> { 566 567 #region Type-level Constructors 568 #endregion 569 570 #region Instance Constructors 571 public InvalidErrorTable() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 572 internal InvalidErrorTable(Tag flag, StackTrace trace) => (Var, Backtrace) = (flag, trace); 573 #endregion 574 575 #region Type-level Fields 576 #endregion 577 578 #region Instance Fields 579 [FieldOffset(0)] public readonly Tag Var; 580 [FieldOffset(8)] public readonly StackTrace Backtrace; 581 #endregion 582 583 #region Type-level Properties 584 #endregion 585 586 #region Instance Properties 587 public readonly Var12 Variant => (Var12)Var; 588 public readonly StackTrace Variant0 => Var == Tag.Database_is_readonly ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Database_is_readonly!"); 589 public readonly StackTrace Variant1 => Var == Tag.Does_not_contain_7_columns ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Does_not_contain_7_columns!"); 590 public readonly StackTrace Variant2 => Var == Tag.First_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128 ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not First_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128!"); 591 public readonly StackTrace Variant3 => Var == Tag.Second_column_not_a_non_nullable_non_encrypted_non_computed_datetimeoffset_7 ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Second_column_not_a_non_nullable_non_encrypted_non_computed_datetimeoffset_7!"); 592 public readonly StackTrace Variant4 => Var == Tag.Third_column_not_a_non_nullable_non_encrypted_non_computed_smallint ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Third_column_not_a_non_nullable_non_encrypted_non_computed_smallint!"); 593 public readonly StackTrace Variant5 => Var == Tag.Fourth_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128 ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Third_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128!"); 594 public readonly StackTrace Variant6 => Var == Tag.Fifth_column_not_a_nullable_non_encrypted_non_computed_nvarchar_max ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Fourth_column_not_a_nullable_non_encrypted_non_computed_nvarchar_max!"); 595 public readonly StackTrace Variant7 => Var == Tag.Sixth_column_not_a_nullable_non_computed_nvarchar_max ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Fifth_column_not_a_nullable_non_computed_nvarchar_max!"); 596 public readonly StackTrace Variant8 => Var == Tag.Seventh_column_not_a_nullable_non_computed_varbinary_max ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Sixth_column_not_a_nullable_non_computed_varbinary_max!"); 597 public readonly StackTrace Variant9 => Var == Tag.Contains_child_objects ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Contains_child_objects!"); 598 public readonly StackTrace Variant10 => Var == Tag.Does_not_contain_non_unique_clustered_index_whose_index_keys_are_the_first_three_columns ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Does_not_contain_non_unique_clustered_index_whose_index_keys_are_the_first_three_columns!"); 599 public readonly StackTrace Variant11 => Var == Tag.Contains_non_clustered_indexes ? Backtrace : throw new InvalidOperationException($"The InvalidErrorTable variant, {Var.ToString()}, is not Contains_non_clustered_indexes!"); 600 #endregion 601 602 #region Type-level Functions 603 #endregion 604 605 #region Instance Functions 606 public override readonly bool Equals(object? _) => false; 607 public override readonly int GetHashCode() => 0; 608 public readonly InvalidErrorTable Into() => this; 609 public readonly string IntoString() => ToString(); 610 readonly string IInto<string>.Into() => IntoString(); 611 public readonly Maybe<IError> Source() => Maybe<IError>.None(); 612 public readonly Maybe<StackTrace> StackTrace() => new(Backtrace); 613 public override readonly string ToString() => $"{Var.ToString()}({Backtrace.ToString()})"; 614 readonly Result<InvalidErrorTable, Bottom> ITryInto<InvalidErrorTable, Bottom>.TryInto() => new(this); 615 readonly Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 616 #endregion 617 618 #region Operators 619 #endregion 620 621 #region Types 622 public enum Tag: ulong { 623 Database_is_readonly = ulong.MinValue, 624 Does_not_contain_7_columns = 1ul, 625 First_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128 = 2ul, 626 Second_column_not_a_non_nullable_non_encrypted_non_computed_datetimeoffset_7 = 3ul, 627 Third_column_not_a_non_nullable_non_encrypted_non_computed_smallint = 4ul, 628 Fourth_column_not_a_non_nullable_non_encrypted_non_computed_nvarchar_128 = 5ul, 629 Fifth_column_not_a_nullable_non_encrypted_non_computed_nvarchar_max = 6ul, 630 Sixth_column_not_a_nullable_non_computed_nvarchar_max = 7ul, 631 Seventh_column_not_a_nullable_non_computed_varbinary_max = 8ul, 632 Contains_child_objects = 9ul, 633 Does_not_contain_non_unique_clustered_index_whose_index_keys_are_the_first_three_columns = 10ul, 634 Contains_non_clustered_indexes = 11ul, 635 } 636 #endregion 637 } 638 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8, Size = 16)] 639 public readonly struct WriteError: ISum<StackTrace, StackTrace, StackTrace>, IError, IInto<WriteError> { 640 641 #region Type-level Constructors 642 #endregion 643 644 #region Instance Constructors 645 public WriteError() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 646 internal WriteError(Tag flag, StackTrace trace) => (Var, Backtrace) = (flag, trace); 647 #endregion 648 649 #region Type-level Fields 650 #endregion 651 652 #region Instance Fields 653 [FieldOffset(0)] public readonly Tag Var; 654 [FieldOffset(8)] public readonly StackTrace Backtrace; 655 #endregion 656 657 #region Type-level Properties 658 #endregion 659 660 #region Instance Properties 661 public readonly Var3 Variant => (Var3)Var; 662 public readonly StackTrace Variant0 => Var == Tag.MaxErrorsExceeded ? Backtrace : throw new InvalidOperationException($"The WriteError variant, {Var.ToString()}, is not MaxErrorsExceeded!"); 663 public readonly StackTrace Variant1 => Var == Tag.TableExpectedToContainEncryptedDataButDoesNot ? Backtrace : throw new InvalidOperationException($"The WriteError variant, {Var.ToString()}, is not TableExpectedToContainEncryptedDataButDoesNot!"); 664 public readonly StackTrace Variant2 => Var == Tag.TableColumnMismatchOrWriteOptionsMustContainAllowEncryptedValueModifications ? Backtrace : throw new InvalidOperationException($"The WriteError variant, {Var.ToString()}, is not TableColumnMismatchOrWriteOptionsMustContainAllowEncryptedValueModifications!"); 665 #endregion 666 667 #region Type-level Functions 668 #endregion 669 670 #region Instance Functions 671 public override readonly bool Equals(object? _) => false; 672 public override readonly int GetHashCode() => 0; 673 public readonly WriteError Into() => this; 674 public readonly string IntoString() => ToString(); 675 readonly string IInto<string>.Into() => IntoString(); 676 public readonly Maybe<IError> Source() => Maybe<IError>.None(); 677 public readonly Maybe<StackTrace> StackTrace() => new(Backtrace); 678 public override readonly string ToString() => $"{Var.ToString()}({Backtrace.ToString()})"; 679 readonly Result<WriteError, Bottom> ITryInto<WriteError, Bottom>.TryInto() => new(this); 680 readonly Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 681 #endregion 682 683 #region Operators 684 #endregion 685 686 #region Types 687 public enum Tag: ulong { 688 MaxErrorsExceeded = ulong.MinValue, 689 TableExpectedToContainEncryptedDataButDoesNot = 1ul, 690 TableColumnMismatchOrWriteOptionsMustContainAllowEncryptedValueModifications = 2ul, 691 } 692 #endregion 693 } 694 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8, Size = 16)] 695 public readonly struct WriteErrorOrTransactionError: ISum<StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace, StackTrace>, IError, IInto<WriteErrorOrTransactionError> { 696 697 #region Type-level Constructors 698 #endregion 699 700 #region Instance Constructors 701 public WriteErrorOrTransactionError() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 702 internal WriteErrorOrTransactionError(Tag flag, StackTrace trace) => (Var, Backtrace) = (flag, trace); 703 #endregion 704 705 #region Type-level Fields 706 #endregion 707 708 #region Instance Fields 709 [FieldOffset(0)] public readonly Tag Var; 710 [FieldOffset(8)] public readonly StackTrace Backtrace; 711 #endregion 712 713 #region Type-level Properties 714 #endregion 715 716 #region Instance Properties 717 public readonly Var7 Variant => (Var7)Var; 718 public readonly StackTrace Variant0 => Var == Tag.MaxErrorsExceeded ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not MaxErrorsExceeded!"); 719 public readonly StackTrace Variant1 => Var == Tag.SqlConnectionIsNotOpen ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not SqlConnectionIsNotOpen!"); 720 public readonly StackTrace Variant2 => Var == Tag.SqlConnectionServerMismatch ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not SqlConnectionServerMismatch!"); 721 public readonly StackTrace Variant3 => Var == Tag.SqlConnectionViolatesAllowEncryptedValueModifications ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not SqlConnectionViolatesAllowEncryptedValueModifications!"); 722 public readonly StackTrace Variant4 => Var == Tag.TableExpectedToContainEncryptedDataButDoesNot ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not TableExpectedToContainEncryptedDataButDoesNot!"); 723 public readonly StackTrace Variant5 => Var == Tag.TableColumnMismatchOrWriteOptionsMustContainAllowEncryptedValueModifications ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not TableColumnMismatchOrWriteOptionsMustContainAllowEncryptedValueModifications!"); 724 public readonly StackTrace Variant6 => Var == Tag.ContainsEncryptedColumnsButConnectionDoesNotHandleEncryptedColumns ? Backtrace : throw new InvalidOperationException($"The WriteErrorOrTransactionError variant, {Var.ToString()}, is not ContainsEncryptedColumnsButConnectionDoesNotHandleEncryptedColumns!"); 725 #endregion 726 727 #region Type-level Functions 728 #endregion 729 730 #region Instance Functions 731 public override readonly bool Equals(object? _) => false; 732 public override readonly int GetHashCode() => 0; 733 public readonly WriteErrorOrTransactionError Into() => this; 734 public readonly string IntoString() => ToString(); 735 readonly string IInto<string>.Into() => IntoString(); 736 public readonly Maybe<IError> Source() => Maybe<IError>.None(); 737 public readonly Maybe<StackTrace> StackTrace() => new(Backtrace); 738 public override readonly string ToString() => $"{Var.ToString()}({Backtrace.ToString()})"; 739 readonly Result<WriteErrorOrTransactionError, Bottom> ITryInto<WriteErrorOrTransactionError, Bottom>.TryInto() => new(this); 740 readonly Result<string, Bottom> ITryInto<string, Bottom>.TryInto() => new(ToString()); 741 #endregion 742 743 #region Operators 744 #endregion 745 746 #region Types 747 public enum Tag: ulong { 748 MaxErrorsExceeded = ulong.MinValue, 749 SqlConnectionIsNotOpen = 1ul, 750 SqlConnectionServerMismatch = 2ul, 751 SqlConnectionViolatesAllowEncryptedValueModifications = 3ul, 752 TableExpectedToContainEncryptedDataButDoesNot = 4ul, 753 TableColumnMismatchOrWriteOptionsMustContainAllowEncryptedValueModifications = 5ul, 754 ContainsEncryptedColumnsButConnectionDoesNotHandleEncryptedColumns = 6ul, 755 } 756 #endregion 757 } 758 public interface IBulkRowError: IError { 759 760 #region Type-level Constructors 761 #endregion 762 763 #region Instance Constructors 764 #endregion 765 766 #region Type-level Fields 767 #endregion 768 769 #region Instance Fields 770 #endregion 771 772 #region Type-level Properties 773 #endregion 774 775 #region Instance Properties 776 public virtual nvarchar Trace => nvarchar.NULL; 777 public virtual nvarchar Message => nvarchar.NULL; 778 public virtual varbinary Data => varbinary.NULL; 779 #endregion 780 781 #region Type-level Functions 782 #endregion 783 784 #region Instance Functions 785 #endregion 786 787 #region Operators 788 #endregion 789 790 #region Types 791 #endregion 792 } 793 [Flags()] 794 public enum WriteOptions: byte { 795 Default = byte.MinValue, 796 KeepIdentity = 1, 797 CheckConstraints = 2, 798 TableLock = 4, 799 KeepNulls = 8, 800 FireTriggers = 16, 801 AllowEncryptedValueModifications = 32, 802 } 803 public static class Helpers { 804 805 #region Type-level Constructors 806 static Helpers() { 807 808 _typeMap = HashMap<ActualType, Type, FNVHasher, RandomFNVHashBuilder>.WithCapacityAndHasher(28u, RandomFNVHashBuilder.New()); 809 _ = _typeMap.Insert(typeof(bigint), typeof(long)); 810 _ = _typeMap.Insert(typeof(binary), typeof(byte[])); 811 _ = _typeMap.Insert(typeof(bit), typeof(bool)); 812 _ = _typeMap.Insert(typeof(@char), typeof(string)); 813 _ = _typeMap.Insert(typeof(date), typeof(DateTime)); 814 _ = _typeMap.Insert(typeof(datetime), typeof(SqlDateTime)); 815 _ = _typeMap.Insert(typeof(datetime2), typeof(DateTime)); 816 _ = _typeMap.Insert(typeof(datetimeoffset), typeof(DateTimeOffset)); 817 _ = _typeMap.Insert(typeof(@decimal), typeof(SqlDecimal)); 818 _ = _typeMap.Insert(typeof(@float), typeof(double)); 819 _ = _typeMap.Insert(typeof(image), typeof(byte[])); 820 _ = _typeMap.Insert(typeof(@int), typeof(int)); 821 _ = _typeMap.Insert(typeof(money), typeof(SqlMoney)); 822 _ = _typeMap.Insert(typeof(nchar), typeof(string)); 823 _ = _typeMap.Insert(typeof(ntext), typeof(string)); 824 _ = _typeMap.Insert(typeof(nvarchar), typeof(string)); 825 _ = _typeMap.Insert(typeof(real), typeof(float)); 826 _ = _typeMap.Insert(typeof(smalldatetime), typeof(SqlDateTime)); 827 _ = _typeMap.Insert(typeof(smallint), typeof(short)); 828 _ = _typeMap.Insert(typeof(smallmoney), typeof(SqlMoney)); 829 _ = _typeMap.Insert(typeof(sql_variant), typeof(object)); 830 _ = _typeMap.Insert(typeof(text), typeof(string)); 831 _ = _typeMap.Insert(typeof(time), typeof(TimeSpan)); 832 _ = _typeMap.Insert(typeof(tinyint), typeof(byte)); 833 _ = _typeMap.Insert(typeof(uniqueidentifier), typeof(Guid)); 834 _ = _typeMap.Insert(typeof(varbinary), typeof(byte[])); 835 _ = _typeMap.Insert(typeof(varchar), typeof(string)); 836 _ = _typeMap.Insert(typeof(xml), typeof(string)); 837 } 838 #endregion 839 840 #region Instance Constructors 841 #endregion 842 843 #region Type-level Fields 844 internal static readonly HashMap<ActualType, Type, FNVHasher, RandomFNVHashBuilder> _typeMap; 845 internal static readonly Fn<NonZeroUshort, int> _nzUshortToInt = (x) => x.Value; 846 static readonly Fn<Maybe<FilterMap<Prod<Column, Maybe<ColumnSort>>, Std.Vec.IntoIterator<Prod<Column, Maybe<ColumnSort>>>, SqlBulkCopyColumnOrderHint>>> _noFilterMap = () => Maybe<FilterMap<Prod<Column, Maybe<ColumnSort>>, Std.Vec.IntoIterator<Prod<Column, Maybe<ColumnSort>>>, SqlBulkCopyColumnOrderHint>>.None(); 847 internal static readonly Fn<WriteErrorOrTransactionError, WriteError> _writeTxnErrToWriteErr = (err) => new WriteError(WriteError.Tag.MaxErrorsExceeded, err.Backtrace); 848 #endregion 849 850 #region Instance Fields 851 #endregion 852 853 #region Type-level Properties 854 #endregion 855 856 #region Instance Properties 857 #endregion 858 859 #region Type-level Functions 860 public static Result<Unit, StringBuilder> BulkWriteFinish<TBulkWriter>(TBulkWriter bulkWriter, UserTable destination, ulong initialBulkTableCount, Maybe<NonZeroUshort> timeout, SessionOptions options, bool identityINSERT, bool commitIfErr) where TBulkWriter: struct, IBulkWriter { 861 862 using var con = Functions.CreateOpenedConnection(in destination.Schema.Database, options, false, Maybe<Uri>.None()); 863 using var txn = con.BeginTransaction(IsolationLevel.Serializable); 864 return BulkWriteFinish(bulkWriter, destination, initialBulkTableCount, txn, timeout, identityINSERT).MapErr((e) => { if (commitIfErr) { txn.Commit(); } else { txn.Rollback(); } return e; }).Map((_) => { txn.Commit(); return new Unit(); }); 865 } 866 // Attempts to INSERT rows from bulkWriter.Destination to destination avoiding any PRIMARY KEY CONSTRAINT, UNIQUE CONSTRAINT, or UNIQUE INDEX violation. 867 // If this succeeds, then bulkWriter.Destination is TRUNCATEd iff initialBulkTableCount is 0 and bulkWriter.CurrentSuccessfullyProcessedCount = the count returned from the INSERT into destination. 868 // If that succeeds, then an error message is generated iff initialBulkTableCount is not 0, bulkWriter.CurrentErrorCount > 0, or bulkWriter.CurrentSuccessfullyProcessedCount ≠ the INSERT count. 869 public static Result<Unit, StringBuilder> BulkWriteFinish<TBulkWriter>(TBulkWriter bulkWriter, UserTable destination, ulong initialBulkTableCount, SqlTransaction txn, Maybe<NonZeroUshort> timeout, bool identityINSERT) where TBulkWriter: struct, IBulkWriter => bulkWriter.Destination.INSERT_INTO_AVOID_UNIQUE_VIOLATIONS(in destination, timeout, txn, identityINSERT).MapErr((e) => new StringBuilder(e.IntoString(), 512)).AndThen((count) => (count == bulkWriter.CurrentSuccessfullyProcessedCount && initialBulkTableCount == ulong.MinValue ? bulkWriter.Destination.TRUNCATE(timeout, txn).MapErr((e) => new StringBuilder(e.IntoString(), 512)) : new(new Unit())).AndThen((_) => { 870 var err = (bulkWriter.CurrentErrorCount > ulong.MinValue ? new StringBuilder($@"There were {bulkWriter.CurrentErrorCount.ToString()} errors when bulk-loading into {bulkWriter.Destination.IntoString()}. 871 This represents ≈ {bulkWriter.CurrentErrorRatio.ToString("N2")} of the total {bulkWriter.CurrentProcessedCount.ToString()} rows that were processed. 872 {bulkWriter.ErrTable.MapOr(string.Empty, (e) => $@"Query {e.IntoString()} for more information. 873 ")}", 512) : new StringBuilder(128)).Append(initialBulkTableCount != ulong.MinValue ? $@"{initialBulkTableCount.ToString()} rows already existed in {bulkWriter.Destination.IntoString()} before processing new rows. This table should be TRUNCATEd after every time it is populated and any issues that may have occurred are resolved. 874 " : string.Empty).Append(count != bulkWriter.CurrentSuccessfullyProcessedCount ? $@"{bulkWriter.CurrentSuccessfullyProcessedCount.ToString()} rows were successfully processed, but {count.ToString()} rows were subsequently INSERTed into {destination.IntoString()}. A PRIMARY KEY CONSTRAINT, UNIQUE CONSTRAINT, or UNIQUE INDEX violation was avoided or there were existing rows in {bulkWriter.Destination.IntoString()} that were also INSERTed. 875 " : string.Empty); 876 return err.Length == 0 ? new Result<Unit, StringBuilder>(new Unit()) : new(err); 877 })); 878 public static Result<Unit, StringBuilder> BulkWriteFinishAlwaysTRUNCATE<TBulkWriter>(TBulkWriter bulkWriter, UserTable destination, SqlTransaction txn, Maybe<NonZeroUshort> timeout, bool identityINSERT) where TBulkWriter: struct, IBulkWriter => bulkWriter.Destination.INSERT_INTO_AVOID_UNIQUE_VIOLATIONS(in destination, timeout, txn, identityINSERT).MapErr((e) => new StringBuilder(e.IntoString(), 512)).AndThen((count) => bulkWriter.Destination.TRUNCATE(timeout, txn).MapErr((e) => new StringBuilder(e.IntoString(), 512)).AndThen((_) => { 879 var err = (bulkWriter.CurrentErrorCount > ulong.MinValue ? new StringBuilder($@"There were {bulkWriter.CurrentErrorCount.ToString()} errors when bulk-loading into {bulkWriter.Destination.IntoString()}. 880 This represents ≈ {bulkWriter.CurrentErrorRatio.ToString("N2")} of the total {bulkWriter.CurrentProcessedCount.ToString()} rows that were processed. 881 {bulkWriter.ErrTable.MapOr(string.Empty, (e) => $@"Query {e.IntoString()} for more information. 882 ")}", 512) : new StringBuilder(128)).Append(count != bulkWriter.CurrentSuccessfullyProcessedCount ? $@"{bulkWriter.CurrentSuccessfullyProcessedCount.ToString()} rows were successfully processed, but {count.ToString()} rows were subsequently INSERTed into {destination.IntoString()}. A PRIMARY KEY CONSTRAINT, UNIQUE CONSTRAINT, or UNIQUE INDEX violation was avoided or there were existing rows in {bulkWriter.Destination.IntoString()} that were also INSERTed. 883 " : string.Empty); 884 return err.Length == 0 ? new Result<Unit, StringBuilder>(new Unit()) : new(err); 885 })); 886 // MUST ensure that txn is null or using con as its SqlConnection! 887 internal static SqlBulkCopy CreateBulkCopy(in UserTable table, WriteOptions writeOptions, Maybe<NonZeroUshort> batchSize, Maybe<NonZeroUshort> timeout, bool enableStreaming, bool isSortedAccordingToClusteredIndex, SqlConnection con, SqlTransaction? txn) { 888 889 SqlBulkCopy blk = new(con, (SqlBulkCopyOptions)writeOptions, txn) { BatchSize = batchSize.MapOr(0, _nzUshortToInt), BulkCopyTimeout = timeout.MapOr(0, _nzUshortToInt), DestinationTableName = $"[{table.Schema.Database.Name.Value}].[{table.Schema.Name.Value}].[{table.Name}]", EnableStreaming = enableStreaming }; 890 var cols = table.IntoIter(); 891 Maybe<Column> maybeCol; 892 Column col; 893 894 while ((maybeCol = cols.Next()).IsSome) { 895 col = maybeCol.Unwrap(); 896 if (col.ComputedInfo.IsNone) { _ = blk.ColumnMappings.Add(col.OrdinalPosition, col.OrdinalPosition); } 897 } 898 // If we are told the data set is sorted and a clustered index exists, inform blk; otherwise do nothing. 899 // Also note that IDENTITY keys MUST be ignored if IDENTITY values are not kept. 900 var dontKeepIdentity = (writeOptions & WriteOptions.KeepIdentity) != WriteOptions.KeepIdentity; 901 _ = (isSortedAccordingToClusteredIndex 902 ? table.GetClusteredIndex().MapOrElse( 903 _noFilterMap, 904 (ix) => new Maybe<FilterMap<Prod<Column, Maybe<ColumnSort>>, Std.Vec.IntoIterator<Prod<Column, Maybe<ColumnSort>>>, SqlBulkCopyColumnOrderHint>>(ix.IntoIter().FilterMap<Std.Vec.IntoIterator<Prod<Column, Maybe<ColumnSort>>>, Prod<Column, Maybe<ColumnSort>>, SqlBulkCopyColumnOrderHint>( 905 (tup) => tup.Item0.IdentityInfo.IsSome && dontKeepIdentity ? Maybe<SqlBulkCopyColumnOrderHint>.None() : new(new(tup.Item0.Name, tup.Item1.Unwrap().Var == ColumnSort.Tag.Descending ? SortOrder.Descending : SortOrder.Ascending))) 906 ) 907 ) 908 : Maybe<FilterMap<Prod<Column, Maybe<ColumnSort>>, Std.Vec.IntoIterator<Prod<Column, Maybe<ColumnSort>>>, SqlBulkCopyColumnOrderHint>>.None() 909 ).MapOr( 910 new Unit(), 911 (hintIter) => { 912 Maybe<SqlBulkCopyColumnOrderHint> val; 913 while ((val = hintIter.Next()).IsSome) { _ = blk.ColumnOrderHints.Add(val.Unwrap()); } 914 return new Unit(); 915 } 916 ); 917 return blk; 918 } 919 // Returns None iff all non-encrypted types match and all encrypted types are modeled directly as a byte[] and where one of which does not have a varbinary plaintext type. 920 // Otherwise returns Some(true) iff all types match or Some(false) if there is at least one type mismatch. 921 internal static Maybe<bool> TypeMatch(FromFn<Type> types, in UserTable table) { 922 923 var columns = table.IntoIter(); 924 var encryptedAsBytes = false; 925 var encrypted = false; 926 return (columns.All((Column c) => types.Next().MapOr(false, (col) => { 927 // The only time a type mismatch is allowed is in the event a byte[] is being used for an encrypted non-varbinary column. 928 // This is due to the fact that the bulk writers allow one to directly write encrypted data into a table bypassing the need 929 // to encrypt the data. Note that directly writing encrypted data via a bulk writer must apply to ALL encrypted columns 930 // (i.e., either all the encrypted columns are written directly in their already encrypted form or none of them are). 931 // Also note that the bulk writers still have to ensure that SqlBulkCopyOptions.AllowEncryptedValueModifications is being used to write to the table. 932 if (c.EncryptionInfo.IsSome) { 933 934 if (encryptedAsBytes) { 935 return col == typeof(varbinary); 936 } else if (encrypted) { 937 return col == c.DataType; 938 } else if (col == typeof(varbinary)) { 939 // Since the plaintext data type may also be a varbinary column, we can only say 940 // the column is meant to represent encrypted data when the actual data type is not a varbinary. 941 if (c.DataType != col) { encryptedAsBytes = true; } 942 return true; 943 } else { 944 // Since varbinary is not the intended data type of the column, 945 // we know all encrypted columns require the data to be encrypted. 946 encrypted = true; 947 return col == c.DataType; 948 } 949 } else { 950 return col == c.DataType; 951 } 952 })) && types.Next().IsNone) ? encryptedAsBytes ? Maybe<bool>.None() : new(true) : new(false); 953 } 954 // MUST ensure that processName and userName are no more than 128 chars in length before calling! 955 internal static Unit WriteErrors(in ErrorTable table, ref Vec<Prod<nvarchar, nvarchar, varbinary>> errs, string processName, string userName, ushort exclusiveMinCount) { 956 957 if (errs.Len > exclusiveMinCount) { 958 using var con = Functions.CreateOpenedConnection(in table.Value.Schema.Database, SessionOptions.DEFAULT, table.ContainsEncrypted, Maybe<Uri>.None()); 959 using SqlBulkCopy blk = new(con, SqlBulkCopyOptions.Default, null) { BatchSize = (int)errs.Len, BulkCopyTimeout = 600, DestinationTableName = $"[{table.Value.Schema.Name.Value}].[{table.Value.Name}]", EnableStreaming = true }; 960 _ = blk.ColumnMappings.Add(0, 0); 961 _ = blk.ColumnMappings.Add(1, 1); 962 _ = blk.ColumnMappings.Add(2, 2); 963 _ = blk.ColumnMappings.Add(3, 3); 964 _ = blk.ColumnMappings.Add(4, 4); 965 _ = blk.ColumnMappings.Add(5, 5); 966 _ = blk.ColumnMappings.Add(6, 6); 967 using var rdr = new ErrorReader(table, errs, processName, userName); 968 blk.WriteToServer(rdr); 969 return errs.Clear(); 970 } 971 return new Unit(); 972 } 973 #endregion 974 975 #region Instance Functions 976 #endregion 977 978 #region Operators 979 #endregion 980 981 #region Types 982 #endregion 983 } 984 public interface IBulkWriter { 985 986 #region Type-level Constructors 987 #endregion 988 989 #region Instance Constructors 990 #endregion 991 992 #region Type-level Fields 993 #endregion 994 995 #region Instance Fields 996 #endregion 997 998 #region Type-level Properties 999 #endregion 1000 1001 #region Instance Properties 1002 public abstract Maybe<ErrorTable> ErrTable { get; } 1003 public abstract UserTable Destination { get; } 1004 public abstract Prod<ulong, double> MaxErrorsAllowed { get; } 1005 public abstract ulong CurrentErrorCount { get; } 1006 public abstract ulong CurrentProcessedCount { get; } 1007 public abstract ulong CurrentSuccessfullyProcessedCount { get; } 1008 public abstract double CurrentErrorRatio { get; } 1009 public abstract bool IsInError { get; } 1010 #endregion 1011 1012 #region Type-level Functions 1013 #endregion 1014 1015 #region Instance Functions 1016 internal abstract void Sealed(); 1017 #endregion 1018 1019 #region Operators 1020 #endregion 1021 1022 #region Types 1023 #endregion 1024 } 1025 #endregion 1026 1027 #region Namespaces 1028 #endregion 1029 } 1030 #endregion