diff options
Diffstat (limited to 'lib/sql-providers/sqlserver')
2 files changed, 175 insertions, 31 deletions
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"> |