/* * Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.Loading.Sql * File: DBCommandHelpers.cs * * DBCommandHelpers.cs is part of VNLib.Plugins.Extensions.Loading.Sql which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Extensions.Loading.Sql is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Plugins.Extensions.Loading.Sql is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.IO; using System.Data; using System.Linq; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace VNLib.Plugins.Extensions.Loading.Sql { /// /// Contains helper methods for loading and configuring SQL database connections /// public static class DBCommandHelpers { private const string MAX_LEN_BYPASS_KEY = "MaxLen"; private const string TIMESTAMP_BYPASS = "TimeStamp"; private static readonly Dictionary TypeMap = new() { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, [typeof(short)] = DbType.Int16, [typeof(ushort)] = DbType.UInt16, [typeof(int)] = DbType.Int32, [typeof(uint)] = DbType.UInt32, [typeof(long)] = DbType.Int64, [typeof(ulong)] = DbType.UInt64, [typeof(float)] = DbType.Single, [typeof(double)] = DbType.Double, [typeof(decimal)] = DbType.Decimal, [typeof(bool)] = DbType.Boolean, [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(DateTime)] = DbType.DateTime, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(byte[])] = DbType.Binary, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, [typeof(short?)] = DbType.Int16, [typeof(ushort?)] = DbType.UInt16, [typeof(int?)] = DbType.Int32, [typeof(uint?)] = DbType.UInt32, [typeof(long?)] = DbType.Int64, [typeof(ulong?)] = DbType.UInt64, [typeof(float?)] = DbType.Single, [typeof(double?)] = DbType.Double, [typeof(decimal?)] = DbType.Decimal, [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, [typeof(DateTime?)] = DbType.DateTime, [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, [typeof(Stream)] = DbType.Binary }; /// /// Gets the from the given .NET runtime type information. /// /// /// The columns or an exception if the type is not supported /// /// public static DbType GetDbType(this DataColumn col) { ArgumentNullException.ThrowIfNull(col); ArgumentNullException.ThrowIfNull(col.DataType, nameof(col.DataType)); if (!TypeMap.TryGetValue(col.DataType, out DbType dbType)) { throw new NotSupportedException($"The type {col.DataType} is not a supporeted database type"); } return dbType; } /// /// Sets the column ordinal index, or column position, within the table. /// /// The entity type /// /// The column's ordinal postion with the database /// The chainable public static IDbColumnBuilder SetPosition(this IDbColumnBuilder builder, int columOridinalIndex) { //Add ourself to the primary keys list builder.ConfigureColumn(col => col.SetOrdinal(columOridinalIndex)); return builder; } /// /// Sets the auto-increment property on the column, this is just a short-cut to /// setting the properties yourself on the column. /// /// The starting (seed) of the increment parameter /// The increment/step parameter /// /// The chainable public static IDbColumnBuilder AutoIncrement(this IDbColumnBuilder builder, int seed = 1, int increment = 1) { //Set the auto-increment features builder.ConfigureColumn(col => { col.AutoIncrement = true; col.AutoIncrementSeed = seed; col.AutoIncrementStep = increment; }); return builder; } /// /// Sets the property to the desired value. This value is set /// via a if defined on the property, this method will override /// that value. /// /// Override the maxium length property on the column /// /// The chainable public static IDbColumnBuilder MaxLength(this IDbColumnBuilder builder, int maxLength) { builder.ConfigureColumn(col => col.MaxLength(maxLength)); return builder; } /// /// Override the /// /// /// /// A value that indicate if you allow null in the column /// The chainable public static IDbColumnBuilder AllowNull(this IDbColumnBuilder builder, bool value) { builder.ConfigureColumn(col => col.AllowDBNull = value); return builder; } /// /// Sets the property to true /// /// The entity type /// /// The chainable public static IDbColumnBuilder Unique(this IDbColumnBuilder builder) { builder.ConfigureColumn(static col => col.Unique = true); return builder; } /// /// Sets the default value for the column /// /// The entity type /// /// The column default value /// The chainable public static IDbColumnBuilder WithDefault(this IDbColumnBuilder builder, object defaultValue) { builder.ConfigureColumn(col => col.DefaultValue = defaultValue); return builder; } /// /// Specifies this column is a RowVersion/TimeStamp for optimistic concurrency for some /// databases. /// /// This vaule is set by default if the entity property specifies a /// /// /// The entity type /// /// The chainable public static IDbColumnBuilder TimeStamp(this IDbColumnBuilder builder) { builder.ConfigureColumn(static col => col.SetTimeStamp()); return builder; } /// /// Sets the column as a PrimaryKey in the table. You may also set the /// on the property. /// /// The entity type /// /// The chainable public static IDbColumnBuilder SetIsKey(this IDbColumnBuilder builder) { //Add ourself to the primary keys list builder.ConfigureColumn(static col => col.AddToPrimaryKeys()); return builder; } /// /// Gets a value that determines if the current column is a primary key /// /// /// True if the collumn is part of the primary keys public static bool IsPrimaryKey(this DataColumn col) { ArgumentNullException.ThrowIfNull(col); ArgumentNullException.ThrowIfNull(col.Table, nameof(col.Table)); return col.Table.PrimaryKey.Contains(col); } /* * I am bypassing the DataColumn.MaxLength property because it does more validation * than we need against the type and can cause unecessary issues, so im just bypassing it * for now */ internal static void MaxLength(this DataColumn column, int length) { column.ExtendedProperties[MAX_LEN_BYPASS_KEY] = length; } /// /// Gets the max length of the column /// /// /// public static int MaxLength(this DataColumn column) { return column.ExtendedProperties.ContainsKey(MAX_LEN_BYPASS_KEY) ? (int)column.ExtendedProperties[MAX_LEN_BYPASS_KEY] : column.MaxLength; } internal static void SetTimeStamp(this DataColumn column) { //We just need to set the key column.ExtendedProperties[TIMESTAMP_BYPASS] = null; } /// /// Gets a value that indicates if the column is a timestamp /// /// /// True if the column is a timestamp column public static bool IsTimeStamp(this DataColumn column) { return column.ExtendedProperties.ContainsKey(TIMESTAMP_BYPASS); } internal static void AddToPrimaryKeys(this DataColumn col) { //Add the column to the table's primary key array List cols = new(col.Table!.PrimaryKey) { col }; //Update the table primary keys now that this col has been added col.Table.PrimaryKey = cols.Distinct().ToArray(); } } }