/*
* 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();
}
}
}