aboutsummaryrefslogtreecommitdiff
path: root/lib/sql-providers
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sql-providers')
-rw-r--r--lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/MYSqlExport.cs270
-rw-r--r--lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj14
-rw-r--r--lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/SQLiteExport.cs210
-rw-r--r--lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj4
-rw-r--r--lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/SqlServerExport.cs201
-rw-r--r--lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj5
6 files changed, 597 insertions, 107 deletions
diff --git a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/MYSqlExport.cs b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/MYSqlExport.cs
index 26fd3a3..efe5410 100644
--- a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/MYSqlExport.cs
+++ b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/MYSqlExport.cs
@@ -23,29 +23,45 @@
*/
using System;
+using System.Data;
+using System.Linq;
+using System.Text;
using System.Text.Json;
using System.Data.Common;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
-using MySql.Data.MySqlClient;
+using MySqlConnector;
using VNLib.Utils.Logging;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Sql;
+using VNLib.Plugins.Extensions.Loading.Sql.DatabaseBuilder;
namespace VNLib.Plugins.Extensions.Sql
{
[ServiceExport]
[ConfigurationName("sql", Required = true)]
- public sealed class MySqlExport(PluginBase plugin, IConfigScope config)
+ public sealed class MySQLExport(PluginBase plugin, IConfigScope config) : IRuntimeDbProvider
{
private async Task<string> BuildConnStringAsync()
{
+ MySqlConnectionStringBuilder sb;
+
//See if the user suggested a raw connection string
if (config.TryGetProperty("connection_string", ps => ps.GetString(), out string? conString))
{
+ sb = new(conString);
+
+ //If the user did not provide a password, try to get it from secret storage
+ if (string.IsNullOrWhiteSpace(sb.Password))
+ {
+ using ISecretResult? password = await plugin.TryGetSecretAsync("db_password");
+ sb.Password = password?.Result.ToString();
+ }
+
return conString!;
}
else if (config.TryGetValue("json", out JsonElement value))
@@ -57,21 +73,18 @@ namespace VNLib.Plugins.Extensions.Sql
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
};
- MySqlConnectionStringBuilder b = value.Deserialize<MySqlConnectionStringBuilder>(opt)!;
+ sb = value.Deserialize<MySqlConnectionStringBuilder>(opt)!;
- //Get the password from the secret manager
+ //Get the db password from the secret manager
using ISecretResult? secret = await plugin.TryGetSecretAsync("db_password");
-
- b.Password = secret?.Result.ToString();
- return b.ConnectionString;
+ sb.Password = secret?.Result.ToString();
}
else
{
//Get the password from the secret manager
using ISecretResult? secret = await plugin.TryGetSecretAsync("db_password");
- // Build connection strin
- return new MySqlConnectionStringBuilder()
+ sb = new()
{
Server = config["hostname"].GetString(),
Database = config["catalog"].GetString(),
@@ -79,36 +92,56 @@ namespace VNLib.Plugins.Extensions.Sql
Pooling = true,
MinimumPoolSize = config.GetValueOrDefault("min_pool_size", p => p.GetUInt32(), 10u),
MaximumPoolSize = config.GetValueOrDefault("max_pool_size", p => p.GetUInt32(), 50u),
- AllowBatch = config.GetValueOrDefault("allow_batch", p => p.GetBoolean(), true),
- ConnectionLifeTime = config.GetValueOrDefault("connection_lifetime", p => p.GetUInt32(), 0u),
- ConnectionTimeout = config.GetValueOrDefault("connection_timeout", p => p.GetUInt32(), 15u),
- Port = config.GetValueOrDefault("port", p => p.GetUInt32(), 3306u),
- PipeName = config.GetValueOrDefault("pipe_name", p => p.GetString(), null),
- AllowLoadLocalInfile = config.GetValueOrDefault("allow_load_local_infile", p => p.GetBoolean(), false),
- AllowLoadLocalInfileInPath = config.GetValueOrDefault("allow_load_local_infile_in_path", p => p.GetString(), null),
-
Password = secret?.Result.ToString(),
+ };
+
+ if (config.TryGetProperty("port", p => p.GetUInt16(), out ushort port))
+ {
+ sb.Port = port;
+ }
+
+ if (config.TryGetProperty("ssl_mode", p => p.GetString(), out string? sslMode)
+ && Enum.TryParse(sslMode, true, out MySqlSslMode mode))
+ {
+ sb.SslMode = mode;
+ }
+
+ if (config.TryGetProperty("connection_lifetime", value => value.GetUInt32(), out uint connLife))
+ {
+ sb.ConnectionLifeTime = connLife;
+ }
+
+ if (config.TryGetProperty("connection_timeout", value => value.GetUInt32(), out uint connTimeout))
+ {
+ sb.ConnectionTimeout = connTimeout;
+ }
+
+ if (config.TryGetProperty("pipe_name", value => value.GetString(), out string? pipeName))
+ {
+ sb.PipeName = pipeName;
+ }
+
+ if (config.TryGetProperty("allow_load_local_infile", value => value.GetBoolean(), out bool allowLoadLocalInfile))
+ {
+ sb.AllowLoadLocalInfile = allowLoadLocalInfile;
+ }
+
+ if (config.TryGetProperty("default_command_timeout", value => value.GetUInt32(), out uint defaultCommandTimeout))
+ {
+ sb.DefaultCommandTimeout = defaultCommandTimeout;
+ }
+
+ if (config.TryGetProperty("interactive_session", value => value.GetBoolean(), out bool interactiveSession))
+ {
+ sb.InteractiveSession = interactiveSession;
}
- .ConnectionString;
}
+
+ return sb.ConnectionString;
}
- /*
- * NOTICE:
- * Function names must be public and must match the SqlConnectionLoader delegate names.
- *
- * GetDbConnection - A sync or async function that takes a configuration scope and
- * returns a DbConnection factory
- *
- * GetDbOptions - A sync or async function that takes a configuration scope and
- * returns a DbConnectionOptions instance
- *
- * GetProviderName - Returns a string that is the provider name for the connection
- */
-
- public string GetProviderName() => "mysql";
-
- public async Task<Func<DbConnection>> GetDbConnectionAsync(IConfigScope sqlConfig)
+ ///<inheritdoc/>
+ public async Task<Func<DbConnection>> GetDbConnectionAsync()
{
//Store local copy of the connection string, probably not the best idea because of the password, but best for now
string connString = await BuildConnStringAsync();
@@ -116,17 +149,18 @@ namespace VNLib.Plugins.Extensions.Sql
return () => new MySqlConnection(connString);
}
- public async Task<DbContextOptions> GetDbOptionsAsync(IConfigScope sqlConfig)
+ ///<inheritdoc/>
+ public async Task<DbContextOptions> GetDbOptionsAsync()
{
//Get the connection string from the configuration
string connString = await BuildConnStringAsync();
//Build the options using the mysql extension method
- DbContextOptionsBuilder b = new();
- b.UseMySQL(connString);
+ DbContextOptionsBuilder b = new();;
+ b.UseMySql(connString, ServerVersion.AutoDetect(connString));
//Write debug loggin to the debug log if the user has it enabled or the plugin is in debug mode
- if (sqlConfig.GetValueOrDefault("debug", p => p.GetBoolean(), false) || plugin.IsDebug())
+ if (config.GetValueOrDefault("debug", p => p.GetBoolean(), false) || plugin.IsDebug())
{
//Write the SQL to the debug log
b.LogTo((v) => plugin.Log.Debug("MySql: {v}", v));
@@ -134,5 +168,165 @@ namespace VNLib.Plugins.Extensions.Sql
return b.Options;
}
+
+ ///<inheritdoc/>
+ public IDBCommandGenerator GetCommandGenerator() => new MsSqlDb();
+
+ internal class MsSqlDb : IDBCommandGenerator
+ {
+ const int MAX_VARIABLE_SIZE = 8000;
+
+ ///<inheritdoc/>
+ public void BuildCreateStatment(StringBuilder builder, DataTable table)
+ {
+ builder.AppendLine("CREATE TABLE IF NOT EXISTS `@tableName` (");
+
+ //Add columns
+ foreach (DataColumn col in table.Columns)
+ {
+ //Get dbType string
+ string dbType;
+
+ //Timestamps/rowversion must be handled specially for MySql optimistic concurrency
+ if (col.IsTimeStamp())
+ {
+ dbType = "TIMESTAMP";
+ }
+ else
+ {
+ dbType = GetTypeStringFromDbType(col);
+ }
+
+ builder.Append('`')
+ .Append(col.ColumnName)
+ .Append("` ")
+ .Append(dbType);
+
+ //If the value is not null, we can specify the default value
+ if (!col.AllowDBNull)
+ {
+ if (!string.IsNullOrWhiteSpace(col.DefaultValue?.ToString()))
+ {
+ builder.Append(" DEFAULT ");
+ builder.Append(col.DefaultValue);
+ }
+ else
+ {
+ //Set not null
+ builder.Append(" NOT NULL");
+ }
+ }
+
+ //Set auto increment
+ if (col.AutoIncrement)
+ {
+ builder.Append(" AUTO_INCREMENT=")
+ .Append(col.AutoIncrementSeed);
+ }
+
+ //Trailing comma
+ builder.AppendLine(",");
+
+ //Set size if defined, we need to bypass column max length
+ if (col.MaxLength() > MAX_VARIABLE_SIZE)
+ {
+ builder.Replace("@size", "MAX");
+ }
+ else if (col.MaxLength() > 0)
+ {
+ builder.Replace("@size", col.MaxLength().ToString());
+ }
+ else
+ {
+ builder.Replace("(@size)", "");
+ }
+ }
+
+ int index = builder.Length;
+ while (builder[--index] != ',')
+ { }
+
+ //Remove the trailing comma
+ builder.Remove(index, 1);
+
+ AddConstraints(builder, table);
+
+ //Close the create table command
+ builder.AppendLine(")");
+
+ //Replaced the table name variables
+ builder.Replace("@tableName", table.TableName);
+ }
+
+ private static void AddConstraints(StringBuilder builder, DataTable table)
+ {
+ DataColumn[] primaryKeys = table.Columns.OfType<DataColumn>()
+ .Where(static c => c.IsPrimaryKey())
+ .ToArray();
+
+ if (primaryKeys.Length > 0)
+ {
+ builder.AppendLine(",")
+ .Append("CONSTRAINT ")
+ .Append(table.TableName)
+ .Append("_pk PRIMARY KEY (")
+ .AppendJoin(", ", primaryKeys.Select(static c => c.ColumnName))
+ .Append(')');
+ }
+
+ //Repeat for unique constraints
+ DataColumn[] uniqueKeys = table.Columns.OfType<DataColumn>()
+ .Where(static c => c.Unique && !c.IsPrimaryKey())
+ .ToArray();
+
+ if (uniqueKeys.Length > 0)
+ {
+ builder.AppendLine(",")
+ .Append("CONSTRAINT ")
+ .Append(table.TableName)
+ .Append("_unique UNIQUE (")
+ .AppendJoin(", ", uniqueKeys.Select(static c => c.ColumnName))
+ .Append(')');
+ }
+
+ builder.AppendLine();
+ }
+
+ ///<inheritdoc/>
+ private static string GetTypeStringFromDbType(DataColumn col)
+ {
+ return col.GetDbType() switch
+ {
+ DbType.AnsiString => "VARCHAR(@size)",
+ DbType.Binary => "VARBINARY(@size)",
+ DbType.Byte => "TINYINT",
+ DbType.Boolean => "BOOL",
+ DbType.Currency => "DECIMAL",
+ DbType.Date => "DATE",
+ DbType.DateTime => "DATETIME",
+ DbType.Decimal => "DECIMAL",
+ DbType.Double => "DOUBLE",
+ DbType.Guid => "VARCHAR(@size)",
+ DbType.Int16 => "SMALLINT",
+ DbType.Int32 => "INT",
+ DbType.Int64 => "BIGINT",
+ DbType.Object => throw new NotSupportedException("A .NET object type is not a supported MySql data-type"),
+ DbType.SByte => "TINYINT",
+ DbType.Single => "FLOAT",
+ DbType.String => "VARCHAR(@size)",
+ DbType.Time => "TIME",
+ DbType.UInt16 => "SMALLINT",
+ DbType.UInt32 => "INT",
+ DbType.UInt64 => "BIGINT",
+ DbType.VarNumeric => throw new NotSupportedException("Variable numeric value is not a supported MySql data-type"),
+ DbType.AnsiStringFixedLength => "TEXT(@size)",
+ DbType.StringFixedLength => "TEXT(@size)",
+ DbType.Xml => "VARCHAR(@size)",
+ DbType.DateTime2 => "DATETIME",
+ DbType.DateTimeOffset => throw new NotSupportedException("DateTimeOffset is not a supported MySql data-type"),
+ _ => throw new NotSupportedException("The desired property data-type is not a supported MySql data-type"),
+ };
+ }
+ }
}
}
diff --git a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj
index dcb4a5c..f9d1ed0 100644
--- a/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj
+++ b/lib/sql-providers/mysql/VNLib.Plugins.Extensions.Loading.Sql.MySql/src/VNLib.Plugins.Extensions.Loading.Sql.MYSql.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
- <AssemblyName>VNLib.Plugins.Extensions.Sql.MySql</AssemblyName>
+ <AssemblyName>VNLib.Plugins.Extensions.Sql.MySQL</AssemblyName>
<RootNamespace>VNLib.Plugins.Extensions.Sql</RootNamespace>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
@@ -12,14 +12,14 @@
</PropertyGroup>
<PropertyGroup>
- <PackageId>VNLib.Plugins.Extensions.Sql.MySql</PackageId>
+ <PackageId>VNLib.Plugins.Extensions.Sql.MySQL</PackageId>
<Authors>Vaughn Nugent</Authors>
<Company>Vaughn Nugent</Company>
- <Product>VNLib.Plugins.Extensions.Sql.MySql</Product>
+ <Product>VNLib.Plugins.Extensions.Sql.MySQL</Product>
<Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
<PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Plugins.Extensions</PackageProjectUrl>
- <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/sql-providers/VNLib.Plugins.Extensions.Sql.MySql</RepositoryUrl>
- <Description>A runtime asset library that provides MySql interfaces for ADO and EFCore SQL server clients</Description>
+ <RepositoryUrl>https://github.com/VnUgE/VNLib.Plugins.Extensions/tree/master/lib/sql-providers/VNLib.Plugins.Extensions.Sql.MySQL</RepositoryUrl>
+ <Description>A runtime asset library that provides MySQL interfaces for ADO and EFCore SQL server clients</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
@@ -45,11 +45,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.0" />
+ <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\..\..\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" />
+ <ProjectReference Include="..\..\..\..\VNLib.Plugins.Extensions.Loading.Sql\src\VNLib.Plugins.Extensions.Loading.Sql.csproj" />
</ItemGroup>
<Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
diff --git a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/SQLiteExport.cs b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/SQLiteExport.cs
index 6f9455a..ff74051 100644
--- a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/SQLiteExport.cs
+++ b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/SQLiteExport.cs
@@ -23,9 +23,12 @@
*/
using System;
+using System.Data;
+using System.Text;
using System.Text.Json;
using System.Data.Common;
using System.Threading.Tasks;
+using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
@@ -33,21 +36,32 @@ using Microsoft.Data.Sqlite;
using VNLib.Utils.Logging;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Sql;
+using VNLib.Plugins.Extensions.Loading.Sql.DatabaseBuilder;
namespace VNLib.Plugins.Extensions.Sql
{
[ServiceExport]
[ConfigurationName("sql", Required = true)]
- public sealed class SQLiteExport(PluginBase plugin, IConfigScope config)
+ public sealed class SQLiteExport(PluginBase plugin, IConfigScope config) : IRuntimeDbProvider
{
private async Task<string> BuildConnStringAsync()
{
+ SqliteConnectionStringBuilder sb;
+
//See if the user suggested a raw connection string
if (config.TryGetProperty("connection_string", ps => ps.GetString(), out string? conString))
{
- return conString!;
+ sb = new(conString);
+
+ //If the user did not provide a password, try to get it from secret storage
+ if (string.IsNullOrWhiteSpace(sb.Password))
+ {
+ using ISecretResult? password = await plugin.TryGetSecretAsync("db_password");
+ sb.Password = password?.Result.ToString();
+ }
}
else if (config.TryGetValue("json", out JsonElement value))
{
@@ -58,13 +72,11 @@ namespace VNLib.Plugins.Extensions.Sql
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
};
- SqliteConnectionStringBuilder b = value.Deserialize<SqliteConnectionStringBuilder>(opt)!;
+ sb = value.Deserialize<SqliteConnectionStringBuilder>(opt)!;
//Get the password from the secret manager
using ISecretResult? secret = await plugin.TryGetSecretAsync("db_password");
-
- b.Password = secret?.Result.ToString();
- return b.ConnectionString;
+ sb.Password = secret?.Result.ToString();
}
else
{
@@ -72,7 +84,7 @@ namespace VNLib.Plugins.Extensions.Sql
using ISecretResult? secret = await plugin.TryGetSecretAsync("db_password");
// Build connection strin
- return new SqliteConnectionStringBuilder()
+ sb = new()
{
DataSource = config["source"].GetString(),
Pooling = true,
@@ -82,35 +94,23 @@ namespace VNLib.Plugins.Extensions.Sql
Mode = config.GetValueOrDefault("mode", p => (SqliteOpenMode)p.GetInt32(), SqliteOpenMode.ReadWriteCreate),
Password = secret?.Result.ToString(),
- }
- .ConnectionString;
+ };
}
+
+ return sb.ConnectionString;
}
-
- /*
- * NOTICE:
- * Function names must be public and must match the SqlConnectionLoader delegate names.
- *
- * GetDbConnection - A sync or async function that takes a configuration scope and
- * returns a DbConnection factory
- *
- * GetDbOptions - A sync or async function that takes a configuration scope and
- * returns a DbConnectionOptions instance
- *
- * GetProviderName - Returns a string that is the provider name for the connection
- */
-
- public string GetProviderName() => "sqlite"; //Use default handler for sqlite db creation
-
- public async Task<Func<DbConnection>> GetDbConnectionAsync(IConfigScope sqlConfig)
- {
+
+ /// <inheritdoc/>
+ public async Task<Func<DbConnection>> GetDbConnectionAsync()
+ {
//Store local copy of the connection string, probably not the best idea because of the password, but best for now
string connString = await BuildConnStringAsync();
return () => new SqliteConnection(connString);
}
- public async Task<DbContextOptions> GetDbOptionsAsync(IConfigScope sqlConfig)
+ /// <inheritdoc/>
+ public async Task<DbContextOptions> GetDbOptionsAsync()
{
//Get the connection string from the configuration
string connString = await BuildConnStringAsync();
@@ -119,7 +119,7 @@ namespace VNLib.Plugins.Extensions.Sql
b.UseSqlite(connString);
//Write debug loggin to the debug log if the user has it enabled or the plugin is in debug mode
- if (sqlConfig.GetValueOrDefault("debug", p => p.GetBoolean(), false) || plugin.IsDebug())
+ if (config.GetValueOrDefault("debug", p => p.GetBoolean(), false) || plugin.IsDebug())
{
//Write the SQL to the debug log
b.LogTo((v) => plugin.Log.Debug("SQLite: {v}", v));
@@ -127,5 +127,157 @@ namespace VNLib.Plugins.Extensions.Sql
return b.Options;
}
+
+ /// <inheritdoc/>
+ public IDBCommandGenerator GetCommandGenerator() => new SqlLiteDb();
+
+ /// <inheritdoc/>
+ public override string ToString() => $"SQLite DB runtime provider : {GetHashCode()}";
+
+ internal sealed class SqlLiteDb : IDBCommandGenerator
+ {
+ public void BuildCreateStatment(StringBuilder builder, DataTable table)
+ {
+ builder.AppendLine("CREATE TABLE IF NOT EXISTS @tableName (");
+
+ List<DataColumn> uniqueCols = new();
+
+ //Add columns
+ foreach (DataColumn col in table.Columns)
+ {
+ //Get dbType string
+ string dbType;
+
+ //Timestamps/rowversion must be handled specially for MySql optimistic concurrency
+ if (col.IsTimeStamp())
+ {
+ dbType = "BINARY(8)";
+ //We may also set the AllowNull property
+ col.AllowDBNull = true;
+ }
+ else
+ {
+ dbType = GetTypeStringFromDbType(col);
+ }
+
+ builder.Append('[')
+ .Append(col.ColumnName)
+ .Append("] ")
+ .Append(dbType);
+
+ //Set primary key contraint
+ if (col.IsPrimaryKey())
+ {
+ builder.Append(" PRIMARY KEY");
+ }
+ //Set unique constraint (only if not pk)
+ else if (col.Unique)
+ {
+ //Add the column to unique list for later
+ uniqueCols.Add(col);
+ }
+
+ //If the value is not null, we can specify the default value
+ if (!col.AllowDBNull)
+ {
+ if (!string.IsNullOrWhiteSpace(col.DefaultValue?.ToString()))
+ {
+ builder.Append(" DEFAULT ");
+ builder.Append(col.DefaultValue);
+ }
+ else
+ {
+ //Set not null
+ builder.Append(" NOT NULL");
+ }
+ }
+
+ //Set auto increment
+ if (col.AutoIncrement)
+ {
+ builder.Append(" AUTOINCREMENT ")
+ .Append(col.AutoIncrementSeed);
+ }
+
+ //Trailing comma
+ builder.AppendLine(",");
+
+ //No sizing for sqlite
+ }
+
+ //Add unique column contraints
+ if (uniqueCols.Count != 0)
+ {
+ builder.Append("UNIQUE(");
+ for (int i = 0; i < uniqueCols.Count;)
+ {
+ //Add column name
+ builder.Append(uniqueCols[i].ColumnName);
+
+ i++;
+
+ //Add trailing commas
+ if (i < uniqueCols.Count)
+ {
+ builder.Append(',');
+ }
+ }
+
+ //Add trailing )
+ builder.AppendLine(")");
+ }
+ else
+ {
+ //remove trailing comma
+ int index = builder.Length;
+ while (builder[--index] != ',')
+ { }
+
+ //Remove the trailing comma
+ builder.Remove(index, 1);
+ }
+
+ //Close the create table command
+ builder.AppendLine(")");
+
+ //Replaced the table name variables
+ builder.Replace("@tableName", table.TableName);
+ }
+
+ private static string GetTypeStringFromDbType(DataColumn col)
+ {
+ return col.GetDbType() switch
+ {
+ DbType.AnsiString => "TEXT",
+ DbType.Binary => "BLOB",
+ DbType.Byte => "INTEGER",
+ DbType.Boolean => "INTEGER",
+ DbType.Currency => "NUMERIC",
+ DbType.Date => "NUMERIC",
+ DbType.DateTime => "NUMERIC",
+ DbType.Decimal => "NUMERIC",
+ DbType.Double => "NUMERIC",
+ DbType.Guid => "TEXT",
+ DbType.Int16 => "INTEGER",
+ DbType.Int32 => "INTEGER",
+ DbType.Int64 => "INTEGER",
+ DbType.Object => throw new NotSupportedException("A .NET object type is not a supported MySql data-type"),
+ DbType.SByte => "INTEGER",
+ DbType.Single => "NUMERIC",
+ DbType.String => "TEXT",
+ DbType.Time => "TEXT",
+ DbType.UInt16 => "INTEGER",
+ DbType.UInt32 => "INTEGER",
+ DbType.UInt64 => "INTEGER",
+ DbType.VarNumeric => "BLOB",
+ DbType.AnsiStringFixedLength => "TEXT",
+ DbType.StringFixedLength => "TEXT",
+ DbType.Xml => "TEXT",
+ DbType.DateTime2 => "NUMERIC",
+ DbType.DateTimeOffset => "NUMERIC",
+ _ => throw new NotSupportedException("The desired property data-type is not a supported MySql data-type"),
+ };
+ }
+ }
}
}
diff --git a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj
index b61f523..d9a38fa 100644
--- a/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj
+++ b/lib/sql-providers/sqlite/VNLib.Plugins.Extensions.Loading.Sql.SQLite/src/VNLib.Plugins.Extensions.Loading.Sql.SQLite.csproj
@@ -48,8 +48,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
</ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\..\..\..\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" />
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\VNLib.Plugins.Extensions.Loading.Sql\src\VNLib.Plugins.Extensions.Loading.Sql.csproj" />
</ItemGroup>
<Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
diff --git a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/SqlServerExport.cs b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/SqlServerExport.cs
index 71f16bf..9f503ed 100644
--- a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/SqlServerExport.cs
+++ b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/SqlServerExport.cs
@@ -23,6 +23,8 @@
*/
using System;
+using System.Data;
+using System.Text;
using System.Text.Json;
using System.Data.Common;
using System.Threading.Tasks;
@@ -33,20 +35,31 @@ using Microsoft.EntityFrameworkCore;
using VNLib.Utils.Logging;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Sql;
+using VNLib.Plugins.Extensions.Loading.Sql.DatabaseBuilder;
namespace VNLib.Plugins.Extensions.Sql
{
[ServiceExport]
[ConfigurationName("sql", Required = true)]
- public sealed class SqlServerExport(PluginBase plugin, IConfigScope config)
+ public sealed class SqlServerExport(PluginBase plugin, IConfigScope config) : IRuntimeDbProvider
{
private async Task<string> BuildConnStringAsync()
{
+ SqlConnectionStringBuilder sb;
+
//See if the user suggested a raw connection string
if (config.TryGetProperty("connection_string", ps => ps.GetString(), out string? conString))
{
- return conString!;
+ sb = new(conString);
+
+ //If the user did not provide a password, try to get it from secret storage
+ if (string.IsNullOrWhiteSpace(sb.Password))
+ {
+ using ISecretResult? password = await plugin.TryGetSecretAsync("db_password");
+ sb.Password = password?.Result.ToString();
+ }
}
else if (config.TryGetValue("json", out JsonElement value))
{
@@ -57,13 +70,12 @@ namespace VNLib.Plugins.Extensions.Sql
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
};
- SqlConnectionStringBuilder b = value.Deserialize<SqlConnectionStringBuilder>(opt)!;
+ //try to get the connection string from the json serialzied object directly
+ sb = value.Deserialize<SqlConnectionStringBuilder>(opt)!;
//Get the password from the secret manager
using ISecretResult? secret = await plugin.TryGetSecretAsync("db_password");
-
- b.Password = secret?.Result.ToString();
- return b.ConnectionString;
+ sb.Password = secret?.Result.ToString();
}
else
{
@@ -71,7 +83,7 @@ namespace VNLib.Plugins.Extensions.Sql
using ISecretResult? secret = await plugin.TryGetSecretAsync("db_password");
// Build connection string
- return new SqlConnectionStringBuilder()
+ sb = new()
{
DataSource = config["hostname"].GetString(),
InitialCatalog = config["catalog"].GetString(),
@@ -99,35 +111,22 @@ namespace VNLib.Plugins.Extensions.Sql
UserInstance = config.GetValueOrDefault("user_instance", p => p.GetBoolean(), false),
Password = secret?.Result.ToString(),
- }
- .ConnectionString;
+ };
}
- }
- /*
- * NOTICE:
- * Function names must be public and must match the SqlConnectionLoader delegate names.
- *
- * GetDbConnection - A sync or async function that takes a configuration scope and
- * returns a DbConnection factory
- *
- * GetDbOptions - A sync or async function that takes a configuration scope and
- * returns a DbConnectionOptions instance
- *
- * GetProviderName - Returns a string that is the provider name for the connection
- */
-
- public string GetProviderName() => "sqlserver";
-
- public async Task<Func<DbConnection>> GetDbConnectionAsync(IConfigScope sqlConfig)
+ return sb.ConnectionString;
+ }
+
+ ///<inheritdoc/>
+ public async Task<Func<DbConnection>> GetDbConnectionAsync()
{
//Store local copy of the connection string, probably not the best idea because of the password, but best for now
string connString = await BuildConnStringAsync();
-
return () => new SqlConnection(connString);
}
- public async Task<DbContextOptions> GetDbOptionsAsync(IConfigScope sqlConfig)
+ ///<inheritdoc/>
+ public async Task<DbContextOptions> GetDbOptionsAsync()
{
//Get the connection string from the configuration
string connString = await BuildConnStringAsync();
@@ -137,7 +136,7 @@ namespace VNLib.Plugins.Extensions.Sql
b.UseSqlServer(connString);
//Write debug loggin to the debug log if the user has it enabled or the plugin is in debug mode
- if (sqlConfig.GetValueOrDefault("debug", p => p.GetBoolean(), false) || plugin.IsDebug())
+ if (config.GetValueOrDefault("debug", p => p.GetBoolean(), false) || plugin.IsDebug())
{
//Write the SQL to the debug log
b.LogTo((v) => plugin.Log.Debug("SqlServer: {v}", v));
@@ -145,5 +144,149 @@ namespace VNLib.Plugins.Extensions.Sql
return b.Options;
}
+
+ ///<inheritdoc/>
+ public IDBCommandGenerator GetCommandGenerator() => new MsSqlDb();
+
+
+ internal class MsSqlDb : IDBCommandGenerator
+ {
+ const int MAX_VARIABLE_SIZE = 8000;
+
+ ///<inheritdoc/>
+ public void BuildCreateStatment(StringBuilder builder, DataTable table)
+ {
+ builder.AppendLine("IF OBJECT_ID(N'[dbo].[@tableName]', N'U') IS NULL");
+ builder.AppendLine("CREATE TABLE [dbo].[@tableName] (");
+
+ //Add columns
+ foreach (DataColumn col in table.Columns)
+ {
+ //Get dbType string
+ string dbType;
+
+ //Timestamps/rowversion must be handled specially for msSql
+ if (col.IsTimeStamp())
+ {
+ dbType = "ROWVERSION";
+ }
+ else
+ {
+ dbType = GetTypeStringFromDbType(col);
+ }
+
+ builder.Append('[')
+ .Append(col.ColumnName)
+ .Append("] ")
+ .Append(dbType);
+
+ //Set primary key contraint
+ if (col.IsPrimaryKey())
+ {
+ builder.Append(" PRIMARY KEY");
+ }
+ //Set unique constraint (only if not pk)
+ else if (col.Unique)
+ {
+ builder.Append(" UNIQUE");
+ }
+
+ //If the value is not null, we can specify the default value
+ if (!col.AllowDBNull)
+ {
+ if (!string.IsNullOrWhiteSpace(col.DefaultValue?.ToString()))
+ {
+ builder.Append(" DEFAULT ");
+ builder.Append(col.DefaultValue);
+ }
+ else
+ {
+ //Set not null
+ builder.Append(" NOT NULL");
+ }
+ }
+
+ //Set auto increment
+ if (col.AutoIncrement)
+ {
+ builder.Append(" IDENTITY(")
+ .Append(col.AutoIncrementSeed)
+ .Append(',')
+ .Append(col.AutoIncrementStep)
+ .Append(')');
+ }
+
+ //Trailing comma
+ builder.AppendLine(",");
+
+
+ //Set size if defined
+ if (col.MaxLength() > MAX_VARIABLE_SIZE)
+ {
+ builder.Replace("@size", "MAX");
+ }
+ else if (col.MaxLength() > 0)
+ {
+ builder.Replace("@size", col.MaxLength().ToString());
+ }
+ else
+ {
+ builder.Replace("(@size)", "");
+ }
+ }
+
+ int index = builder.Length;
+ while (builder[--index] != ',')
+ { }
+
+ //Remove the trailing comma
+ builder.Remove(index, 1);
+
+ //Close the create table command
+ builder.AppendLine(")");
+
+ //Replaced the table name variables
+ builder.Replace("@tableName", table.TableName);
+ }
+
+ ///<inheritdoc/>
+ private static string GetTypeStringFromDbType(DataColumn col)
+ {
+ return col.GetDbType() switch
+ {
+ DbType.AnsiString => "VARCHAR(@size)",
+ DbType.Binary => "VARBINARY(@size)",
+ DbType.Byte => "TINYINT",
+ DbType.Boolean => "BOOL",
+ DbType.Currency => "MONEY",
+ DbType.Date => "DATE",
+ DbType.DateTime => "DATETIME",
+ DbType.Decimal => "DECIMAL",
+ DbType.Double => "DOUBLE",
+ DbType.Guid => "VARCHAR(@size)",
+ DbType.Int16 => "SMALLINT",
+ DbType.Int32 => "INT",
+ DbType.Int64 => "BIGINT",
+ DbType.Object => throw new NotSupportedException("A .NET object type is not a supported MySql data-type"),
+ DbType.SByte => "TINYINT",
+ DbType.Single => "FLOAT",
+ //unicode string support
+ DbType.String => "NVARCHAR(@size)",
+ DbType.Time => "TIME",
+ DbType.UInt16 => "SMALLINT",
+ DbType.UInt32 => "INT",
+ DbType.UInt64 => "BIGINT",
+ DbType.VarNumeric => throw new NotSupportedException("Variable numeric value is not a supported MySql data-type"),
+ DbType.AnsiStringFixedLength => "TEXT(@size)",
+ //unicode text support
+ DbType.StringFixedLength => "NTEXT(@size)",
+ //Define custom xml schema variable
+ DbType.Xml => "XML(@xml_schema_collection)",
+ DbType.DateTime2 => "DATETIME2",
+ DbType.DateTimeOffset => "DATETIMEOFFSET",
+ _ => throw new NotSupportedException("The desired property data-type is not a supported MySql data-type"),
+ };
+ }
+ }
}
}
diff --git a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj
index fea8eee..5b611ca 100644
--- a/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj
+++ b/lib/sql-providers/sqlserver/VNLib.Plugins.Extensions.Loading.Sql.SQLServer/src/VNLib.Plugins.Extensions.Loading.Sql.SQLServer.csproj
@@ -46,10 +46,11 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.1" />
+
</ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\..\..\..\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" />
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\VNLib.Plugins.Extensions.Loading.Sql\src\VNLib.Plugins.Extensions.Loading.Sql.csproj" />
</ItemGroup>
<Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">