aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-02-16 14:51:22 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-02-16 17:25:46 -0500
commitb73f0fdd70d8b560422c80a6ab9bfe96f97db3b3 (patch)
treeb21494d531818f44262d53833aba5f7c6185cd92 /lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
parent54760bfabb36c96f666ca7f77028d0d6a9c812fc (diff)
Squashed commit of the following:
commit 7b2e8b9d659f26d83c3df710056a18a9f3ddaac2 Author: vnugent <public@vaughnnugent.com> Date: Fri Feb 16 14:21:08 2024 -0500 fix: revert mysql lib back to Pomelo and export command generators commit d72bd53e20770be4ced0d627567ecf567d1ce9f4 Author: vnugent <public@vaughnnugent.com> Date: Mon Feb 12 18:34:52 2024 -0500 refactor: #1 convert sql libraries to assets for better code splitting commit 736b873e32447254b3aadbb5c6252818c25e8fd4 Author: vnugent <public@vaughnnugent.com> Date: Sun Feb 4 01:30:25 2024 -0500 submit pending changes
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs')
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs335
1 files changed, 15 insertions, 320 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
index 4897b59..58ca934 100644
--- a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/SqlDbConnectionLoader.cs
@@ -23,21 +23,16 @@
*/
using System;
-using System.Linq;
using System.Data;
using System.Text;
using System.Data.Common;
using System.Threading.Tasks;
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using VNLib.Utils.Logging;
-using VNLib.Utils.Resources;
-using VNLib.Utils.Extensions;
using VNLib.Plugins.Extensions.Loading.Sql.DatabaseBuilder;
-using VNLib.Plugins.Extensions.Loading.Sql.DatabaseBuilder.Helpers;
namespace VNLib.Plugins.Extensions.Loading.Sql
{
@@ -49,10 +44,7 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
{
public const string SQL_CONFIG_KEY = "sql";
public const string DB_PASSWORD_KEY = "db_password";
- public const string SQL_PROVIDER_DLL_KEY = "provider";
-
- private const string MAX_LEN_BYPASS_KEY = "MaxLen";
- private const string TIMESTAMP_BYPASS = "TimeStamp";
+ public const string SQL_PROVIDER_DLL_KEY = "provider";
/// <summary>
@@ -82,15 +74,17 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
/// <exception cref="ObjectDisposedException"></exception>
public static IAsyncLazy<Func<DbConnection>> GetConnectionFactoryAsync(this PluginBase plugin)
{
- plugin.ThrowIfUnloaded();
-
- //Get the provider singleton
- DbProvider provider = LoadingExtensions.GetOrCreateSingleton(plugin, GetDbPovider);
+ IRuntimeDbProvider provider = plugin.GetDbProvider();
+ return provider.GetDbConnectionAsync().AsLazy();
+ }
- return provider.ConnectionFactory.Value.AsLazy();
+ private static IRuntimeDbProvider GetDbProvider(this PluginBase plugin)
+ {
+ plugin.ThrowIfUnloaded();
+ return LoadingExtensions.GetOrCreateSingleton(plugin, LoadDbProvider);
}
- private static DbProvider GetDbPovider(PluginBase plugin)
+ private static IRuntimeDbProvider LoadDbProvider(PluginBase plugin)
{
//Get the sql configuration scope
IConfigScope sqlConf = plugin.GetConfig(SQL_CONFIG_KEY);
@@ -103,9 +97,7 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
* insead of forcing a shared interface. This allows the external library to be
* more flexible and slimmer.
*/
- object instance = plugin.CreateServiceExternal<object>(dllPath);
-
- return new(instance, sqlConf);
+ return plugin.CreateServiceExternal<IRuntimeDbProvider>(dllPath);
}
/// <summary>
@@ -137,12 +129,8 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
/// <remarks>If plugin is in debug mode, writes log data to the default log</remarks>
public static IAsyncLazy<DbContextOptions> GetContextOptionsAsync(this PluginBase plugin)
{
- plugin.ThrowIfUnloaded();
-
- //Get the provider singleton
- DbProvider provider = LoadingExtensions.GetOrCreateSingleton(plugin, GetDbPovider);
-
- return provider.OptionsFactory.Value.AsLazy();
+ IRuntimeDbProvider provider = plugin.GetDbProvider();
+ return provider.GetDbOptionsAsync().AsLazy();
}
/// <summary>
@@ -179,10 +167,11 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
dbCreator.OnDatabaseCreating(builder, state);
//Get the abstract database from the connection type
- IDBCommandGenerator cb = GetCmdGenerator(plugin);
+ IRuntimeDbProvider dbp = plugin.GetDbProvider();
+ IDBCommandGenerator cb = dbp.GetCommandGenerator();
//Wait for the connection factory to load
- Func<DbConnection> dbConFactory = await GetConnectionFactoryAsync(plugin);
+ Func<DbConnection> dbConFactory = await dbp.GetDbConnectionAsync();
//Create a new db connection
await using DbConnection connection = dbConFactory();
@@ -221,299 +210,5 @@ namespace VNLib.Plugins.Extensions.Loading.Sql
//All done!
plugin.Log.Debug("Successfully created tables for {type}", typeof(T).Name);
}
-
- #region ColumnExtensions
-
- /// <summary>
- /// Sets the column as a PrimaryKey in the table. You may also set the
- /// <see cref="KeyAttribute"/> on the property.
- /// </summary>
- /// <typeparam name="T">The entity type</typeparam>
- /// <param name="builder"></param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> SetIsKey<T>(this IDbColumnBuilder<T> builder)
- {
- //Add ourself to the primary keys list
- builder.ConfigureColumn(static col => col.AddToPrimaryKeys());
- return builder;
- }
-
- /// <summary>
- /// Sets the column ordinal index, or column position, within the table.
- /// </summary>
- /// <typeparam name="T">The entity type</typeparam>
- /// <param name="builder"></param>
- /// <param name="columOridinalIndex">The column's ordinal postion with the database</param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> SetPosition<T>(this IDbColumnBuilder<T> builder, int columOridinalIndex)
- {
- //Add ourself to the primary keys list
- builder.ConfigureColumn(col => col.SetOrdinal(columOridinalIndex));
- return builder;
- }
-
- /// <summary>
- /// Sets the auto-increment property on the column, this is just a short-cut to
- /// setting the properties yourself on the column.
- /// </summary>
- /// <param name="seed">The starting (seed) of the increment parameter</param>
- /// <param name="increment">The increment/step parameter</param>
- /// <param name="builder"></param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> AutoIncrement<T>(this IDbColumnBuilder<T> 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;
- }
-
- /// <summary>
- /// Sets the <see cref="DataColumn.MaxLength"/> property to the desired value. This value is set
- /// via a <see cref="MaxLengthAttribute"/> if defined on the property, this method will override
- /// that value.
- /// </summary>
- /// <param name="maxLength">Override the maxium length property on the column</param>
- /// <param name="builder"></param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> MaxLength<T>(this IDbColumnBuilder<T> builder, int maxLength)
- {
- //Set the max-length
- builder.ConfigureColumn(col => col.MaxLength(maxLength));
- return builder;
- }
-
- /// <summary>
- /// Override the <see cref="DataColumn.AllowDBNull"/>
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="builder"></param>
- /// <param name="value">A value that indicate if you allow null in the column</param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> AllowNull<T>(this IDbColumnBuilder<T> builder, bool value)
- {
- builder.ConfigureColumn(col => col.AllowDBNull = value);
- return builder;
- }
-
- /// <summary>
- /// Sets the <see cref="DataColumn.Unique"/> property to true
- /// </summary>
- /// <typeparam name="T">The entity type</typeparam>
- /// <param name="builder"></param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> Unique<T>(this IDbColumnBuilder<T> builder)
- {
- builder.ConfigureColumn(static col => col.Unique = true);
- return builder;
- }
-
- /// <summary>
- /// Sets the default value for the column
- /// </summary>
- /// <typeparam name="T">The entity type</typeparam>
- /// <param name="builder"></param>
- /// <param name="defaultValue">The column default value</param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> WithDefault<T>(this IDbColumnBuilder<T> builder, object defaultValue)
- {
- builder.ConfigureColumn(col => col.DefaultValue = defaultValue);
- return builder;
- }
-
- /// <summary>
- /// Specifies this column is a RowVersion/TimeStamp for optimistic concurrency for some
- /// databases.
- /// <para>
- /// This vaule is set by default if the entity property specifies a <see cref="TimestampAttribute"/>
- /// </para>
- /// </summary>
- /// <typeparam name="T">The entity type</typeparam>
- /// <param name="builder"></param>
- /// <returns>The chainable <see cref="IDbColumnBuilder{T}"/></returns>
- public static IDbColumnBuilder<T> TimeStamp<T>(this IDbColumnBuilder<T> builder)
- {
- builder.ConfigureColumn(static col => col.SetTimeStamp());
- return builder;
- }
-
- #endregion
-
- private static IDBCommandGenerator GetCmdGenerator(PluginBase plugin)
- {
- //Get the provider singleton
- DbProvider provider = LoadingExtensions.GetOrCreateSingleton(plugin, GetDbPovider);
-
- //See if the provider has a command builder function, otherwise try to use known defaults
- if (provider.HasCommandBuilder)
- {
- return provider.CommandGenerator;
- }
- else if (string.Equals(provider.ProviderName, "sqlserver", StringComparison.OrdinalIgnoreCase))
- {
- return new MsSqlDb();
- }
- else if (string.Equals(provider.ProviderName, "mysql", StringComparison.OrdinalIgnoreCase))
- {
- return new MySqlDb();
- }
- else if (string.Equals(provider.ProviderName, "sqlite", StringComparison.OrdinalIgnoreCase))
- {
- return new SqlLiteDb();
- }
- else
- {
- throw new NotSupportedException("This library does not support the abstract databse backend");
- }
- }
-
- internal static bool IsPrimaryKey(this DataColumn col) => 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;
- }
-
- internal 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;
- }
-
- internal 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<DataColumn> cols = new(col.Table!.PrimaryKey)
- {
- col
- };
-
- //Update the table primary keys now that this col has been added
- col.Table.PrimaryKey = cols.Distinct().ToArray();
- }
-
- internal sealed class DbProvider(object instance, IConfigScope sqlConfig)
- {
- public delegate Task<Func<DbConnection>> AsynConBuilderDelegate(IConfigScope sqlConf);
- public delegate Func<DbConnection> SyncConBuilderDelegate(IConfigScope sqlConf);
- public delegate DbContextOptions SyncOptBuilderDelegate(IConfigScope sqlConf);
- public delegate Task<DbContextOptions> AsynOptBuilderDelegate(IConfigScope sqlConf);
- public delegate void BuildTableStringDelegate(StringBuilder builder, DataTable table);
- public delegate string ProviderNameDelegate();
-
-
- public object Provider { get; } = instance;
-
- public IConfigScope SqlConfig { get; } = sqlConfig;
-
- /// <summary>
- /// A lazy async connection factory. When called, may cause invocation in the external library,
- /// but only once.
- /// </summary>
- public readonly Lazy<Task<Func<DbConnection>>> ConnectionFactory = new(() => GetConnections(instance, sqlConfig));
-
- /// <summary>
- /// A lazy async options factory. When called, may cause invocation in the external library,
- /// but only once.
- /// </summary>
- public readonly Lazy<Task<DbContextOptions>> OptionsFactory = new(() => GetOptions(instance, sqlConfig));
-
- /// <summary>
- /// Gets the extern command generator for the external library
- /// </summary>
- public readonly IDBCommandGenerator CommandGenerator = new ExternCommandGenerator(instance);
-
- /// <summary>
- /// Gets the provider name from the external library
- /// </summary>
- public readonly ProviderNameDelegate ProviderNameFunc = ManagedLibrary.GetMethod<ProviderNameDelegate>(instance, "GetProviderName");
-
- /// <summary>
- /// Gets a value indicating if the external library has a command builder
- /// </summary>
- public bool HasCommandBuilder => (CommandGenerator as ExternCommandGenerator)!.BuildTableString is not null;
-
- /// <summary>
- /// Gets the provider name from the external library
- /// </summary>
- public string ProviderName => ProviderNameFunc.Invoke();
-
- /*
- * Methods below are designed to be called within a lazy/defered context and possible awaited
- * by mutliple threads. This causes data to be only loaded once, and then cached for future calls.
- */
-
- private static Task<Func<DbConnection>> GetConnections(object instance, IConfigScope sqlConfig)
- {
- //Connection builder functions
- SyncConBuilderDelegate? SyncBuilder = ManagedLibrary.TryGetMethod<SyncConBuilderDelegate>(instance, "GetDbConnection");
-
- //try sync first
- if (SyncBuilder is not null)
- {
- return Task.FromResult(SyncBuilder.Invoke(sqlConfig));
- }
-
- //If no sync function force call async, but try to schedule it on a new thread
- AsynConBuilderDelegate? AsynConnectionBuilder = ManagedLibrary.GetMethod<AsynConBuilderDelegate>(instance, "GetDbConnectionAsync");
- return Task.Run(() => AsynConnectionBuilder.Invoke(sqlConfig));
- }
-
- private static Task<DbContextOptions> GetOptions(object instance, IConfigScope sqlConfig)
- {
- //Options builder functions
- SyncOptBuilderDelegate? SyncBuilder = ManagedLibrary.TryGetMethod<SyncOptBuilderDelegate>(instance, "GetDbOptions");
-
- //try sync first
- if (SyncBuilder is not null)
- {
- return Task.FromResult(SyncBuilder.Invoke(sqlConfig));
- }
-
- //If no sync function force call async, but try to schedule it on a new thread
- AsynOptBuilderDelegate? AsynOptionsBuilder = ManagedLibrary.GetMethod<AsynOptBuilderDelegate>(instance, "GetDbOptionsAsync");
- return Task.Run(() => AsynOptionsBuilder.Invoke(sqlConfig));
- }
-
- private sealed class ExternCommandGenerator(object instance) : IDBCommandGenerator
- {
- public BuildTableStringDelegate? BuildTableString = ManagedLibrary.TryGetMethod<BuildTableStringDelegate>(instance, "BuildCreateStatment");
-
-
- public void BuildCreateStatment(StringBuilder builder, DataTable table)
- {
- if(BuildTableString is not null)
- {
- BuildTableString.Invoke(builder, table);
- }
- else
- {
- throw new NotSupportedException("The external library does not support table creation");
- }
- }
- }
- }
}
}