diff --git a/docs/code-cleanup-review.md b/docs/code-cleanup-review.md new file mode 100644 index 00000000..e68a6092 --- /dev/null +++ b/docs/code-cleanup-review.md @@ -0,0 +1,31 @@ +# Code cleanup review — 2026-09-22 + +Reviewed from freshly fetched `origin/master` (`ed162ba`). This is a focused source and build review, not certification of every database provider. + +## Changes in this cleanup + +- **Factory resolution:** stop swallowing exceptions thrown by registered factory delegates. Missing registrations still fall through to the system registry and reflection; actual initialization failures retain their original exception. Use a concurrent registry for registration, lookup and provider-name enumeration. +- **Migration names:** preserve the first character of lowercase names and handle empty or numeric-only names without substring exceptions. These names are used in loader tracing and migration logging. +- **SQL type names:** compare SQL names independently of the current culture, avoid repeated enumeration, and expand length/precision/scale placeholders when falling back to the default type mapping. +- **Duplication:** share loader trace output, delegate the `DbType` registration overloads to the `MigratorDbType` implementation, and use a hash set for duplicate migration-version detection. +- **Unnecessary reflection:** instantiate known dialect classes directly. The existing null result for unknown provider values remains unchanged. +- **Dead code:** remove three files containing only commented-out test code: the old MySQL provider fixture, SQLite PRAGMA fixture and schema-dumper fixture. None contributed executable tests. +- **Small hygiene fixes:** remove a duplicate MySQL import, dispose the generic test provider and directly index the built fluent-operation list. + +## Follow-up changes + +- **Provider default parsing:** moved SQL Server, Oracle and PostgreSQL default interpretation into separate internal parsers. Shared text classification, numeric conversions and hexadecimal decoding remove duplicated conversion logic while retaining dialect-specific date, interval, cast and GUID handling. Removed unreachable string branches and PostgreSQL's empty primary-key branch. +- **Parser corrections:** numeric SQL `NULL` defaults remain null; malformed odd-length binary literals now fail instead of silently losing their last digit. PostgreSQL numeric literals no longer depend on whether their text contains the table name; SQL expressions still pass through as `RawSql`. +- **Fixture inheritance:** removed an empty SQLite fixture with a hidden setup method; renamed the table-plus-primary-key helper so it no longer hides the base helper; removed SQL Server's identical copy of the inherited compound-primary-key test. +- **Duplicate scenarios:** retained one identity/primary-key metadata test and removed two identical copies in the generic fixture. This removes two repeated cases from each of four derived provider suites. No distinct assertions were removed. +- **CLI structure:** extracted typed options and command handlers from the entry point. Driver names and factories now use one mapping. Options are validated before assembly loading or opening a database, including options passed to offline/list commands. Exit codes and credential-safe errors remain covered. +- **Warning noise:** deliberate calls to three obsolete APIs now go through the test-only `LegacyMetadata` helper, with suppression scoped to those calls. Production obsolete-API warnings and complexity diagnostics remain enabled. + +## Validation and remaining limits + +- Solution rebuild passes. The final build retains production complexity and compatibility warnings; the CLI complexity and hidden-test-member warnings are gone. +- Unit suite: 175 passed, zero skipped, including 53 catalog parser cases and CLI argument/filter/offline-output coverage. +- SQLite suite: 203 passed, zero skipped. The previous 205 included two duplicate generic cases. +- Parser cases check CLR types as well as values under a non-English culture, SQL expressions, nulls, SQL Server datetime conversion, Oracle GUID byte order, PostgreSQL casts/timezones/intervals, and invalid binary input. +- SQL Server, PostgreSQL and Oracle catalog queries still need their live CI suites after this refactor. Docker is unavailable locally, so no server-backed matrix result is claimed. Test discovery/count reporting is dynamic and does not require hardcoded count updates. +- The provider catalog/type-mapping methods remain substantial; this change separates and tests default parsing without rewriting their database queries. Other existing complexity warnings in the migration runner and SQLite reconstruction/parser code remain future cleanup work. diff --git a/src/Migrator.Tests/CatalogDefaultParsingTests.cs b/src/Migrator.Tests/CatalogDefaultParsingTests.cs new file mode 100644 index 00000000..b0a83ace --- /dev/null +++ b/src/Migrator.Tests/CatalogDefaultParsingTests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Data; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers.Impl.Oracle; +using DotNetProjects.Migrator.Providers.Impl.PostgreSQL; +using DotNetProjects.Migrator.Providers.Impl.SqlServer; +using NUnit.Framework; + +namespace Migrator.Tests; + +public class CatalogDefaultParsingTests +{ + private static IEnumerable Literals() + { + foreach (var dialect in new[] { "SqlServer", "Oracle", "PostgreSQL" }) + { + yield return new TestCaseData(dialect, DbType.String, "'O''Brien (x)'", "O'Brien (x)"); + yield return new TestCaseData(dialect, DbType.Int32, "42", 42L); + yield return new TestCaseData(dialect, DbType.Int16, "'-42'", -42L); + yield return new TestCaseData(dialect, DbType.Decimal, "'12.50'", 12.50m); + yield return new TestCaseData(dialect, DbType.Double, "1.25", 1.25d); + yield return new TestCaseData(dialect, DbType.Single, "1.25", dialect == "Oracle" ? (object)1.25f : 1.25d); + yield return new TestCaseData(dialect, DbType.Guid, "'00112233-4455-6677-8899-aabbccddeeff'", Guid.Parse("00112233-4455-6677-8899-aabbccddeeff")); + } + yield return new TestCaseData("SqlServer", DbType.Int64, "(('42'))", 42L); + yield return new TestCaseData("SqlServer", DbType.Byte, "((255))", (byte)255); + yield return new TestCaseData("SqlServer", DbType.UInt64, "((18446744073709551615))", ulong.MaxValue); + yield return new TestCaseData("SqlServer", DbType.Boolean, "((1))", true); + yield return new TestCaseData("SqlServer", DbType.Boolean, "('FALSE')", false); + yield return new TestCaseData("SqlServer", DbType.Binary, "(0x00A1ff)", new byte[] { 0, 161, 255 }); + yield return new TestCaseData("SqlServer", DbType.DateTime, "(CONVERT([datetime],'2000-01-02 03:04:05.123',(121)))", new DateTime(2000, 1, 2, 3, 4, 5, 123, DateTimeKind.Utc)); + yield return new TestCaseData("SqlServer", DbType.Time, "'12:34:56.1234567'", new TimeOnly(12, 34, 56).Add(TimeSpan.FromTicks(1234567))); + yield return new TestCaseData("Oracle", DbType.Binary, "HEXTORAW('00A1ff')", new byte[] { 0, 161, 255 }); + yield return new TestCaseData("Oracle", DbType.Guid, "HEXTORAW('00112233445566778899AABBCCDDEEFF')", Guid.Parse("00112233-4455-6677-8899-aabbccddeeff")); + yield return new TestCaseData("Oracle", DbType.DateTime, "TO_TIMESTAMP('2000-01-02 03:04:05.123','YYYY-MM-DD HH24:MI:SS.FF')", new DateTime(2000, 1, 2, 3, 4, 5, 123, DateTimeKind.Utc)); + yield return new TestCaseData("Oracle", DbType.Boolean, "TRUE", true); + yield return new TestCaseData("PostgreSQL", DbType.Int32, "'42'::integer", 42L); + yield return new TestCaseData("PostgreSQL", DbType.String, "'O''Brien'::text", "O'Brien"); + yield return new TestCaseData("PostgreSQL", DbType.Binary, "'\\x00a1ff'::bytea", new byte[] { 0, 161, 255 }); + yield return new TestCaseData("PostgreSQL", DbType.Time, "'12:34:56.1234567'::time without time zone", new TimeOnly(12, 34, 56).Add(TimeSpan.FromTicks(1234567))); + yield return new TestCaseData("PostgreSQL", DbType.DateTime, "'2000-01-02 03:04:05'::timestamp without time zone", new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc)); + yield return new TestCaseData("PostgreSQL", DbType.DateTimeOffset, "'2000-01-02 03:04:05+02'::timestamp with time zone", new DateTimeOffset(2000, 1, 2, 3, 4, 5, TimeSpan.FromHours(2))); + yield return new TestCaseData("PostgreSQL", DbType.Boolean, "false", false); + } + + [TestCaseSource(nameof(Literals))] + [SetCulture("de-DE")] + public void CatalogLiteralsRetainTheirValuesAndClrTypes(string dialect, DbType type, string sql, object expected) + { + var column = new Column("Value", type); + Apply(dialect, column, sql); + Assert.That(column.DefaultValue, Is.EqualTo(expected)); + Assert.That(column.DefaultValue.GetType(), Is.EqualTo(expected.GetType())); + } + + [TestCase("SqlServer", "(newid())")] + [TestCase("Oracle", "SYS_GUID()")] + [TestCase("PostgreSQL", "gen_random_uuid()")] + public void ExpressionsRemainSql(string dialect, string sql) + { + var column = new Column("Value", DbType.Guid); + Apply(dialect, column, sql); + Assert.That(column.DefaultValue, Is.TypeOf()); + Assert.That(column.DefaultValue.ToString(), Is.EqualTo(sql)); + } + + [TestCase("SqlServer", "(0xABC)")] + [TestCase("Oracle", "HEXTORAW('ABC')")] + [TestCase("PostgreSQL", "'\\xabc'::bytea")] + public void OddLengthBinaryDefaultsAreRejectedInsteadOfTruncated(string dialect, string sql) + { + Assert.Throws(() => Apply(dialect, new Column("Value", DbType.Binary), sql)); + } + + [TestCase("schema.sequence.nextval")] + [TestCase("\"ISEQ$$123\".nextval")] + [TestCase("NULL")] + public void OracleIdentityExpressionsAndNullDoNotBecomeDefaults(string sql) + { + var column = new Column("Value", DbType.Int64); + OracleColumnDefault.Apply(column, sql); + Assert.That(column.DefaultValue, Is.Null); + } + + private static void Apply(string dialect, Column column, string sql) + { + switch (dialect) + { + case "SqlServer": SqlServerColumnDefault.Apply(column, sql); break; + case "Oracle": OracleColumnDefault.Apply(column, sql); break; + case "PostgreSQL": PostgreSqlColumnDefault.Apply(column, sql); break; + default: throw new ArgumentOutOfRangeException(nameof(dialect)); + } + } + + [TestCase("SqlServer", "((NULL))")] + [TestCase("PostgreSQL", "NULL")] + public void NumericNullDefaultsRemainNull(string dialect, string sql) + { + var column = new Column("Value", DbType.Int32); + Apply(dialect, column, sql); + Assert.That(column.DefaultValue, Is.Null); + } + + [TestCase("-51:02:03.1234567", -1837231234567L)] + [TestCase("51:02:03.1234567", 1837231234567L)] + public void PostgreSqlIntervalsRetainSignDaysAndFractionalTicks(string literal, long ticks) + { + var column = new Column("Value", MigratorDbType.Interval); + PostgreSqlColumnDefault.Apply(column, "'" + literal + "'::interval"); + Assert.That(column.DefaultValue, Is.EqualTo(TimeSpan.FromTicks(ticks))); + } +} diff --git a/src/Migrator.Tests/CoreCleanupRegressionTests.cs b/src/Migrator.Tests/CoreCleanupRegressionTests.cs new file mode 100644 index 00000000..12e1913d --- /dev/null +++ b/src/Migrator.Tests/CoreCleanupRegressionTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Data; +using System.Data.Common; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Providers; +using Microsoft.Data.Sqlite; +using NUnit.Framework; +using ProviderFactories = DotNetProjects.Migrator.Providers.DbProviderFactories; + +namespace Migrator.Tests; + +public class CoreCleanupRegressionTests +{ + [TestCase("CreateATable", "Create a table")] + [TestCase("createTable", "Create table")] + [TestCase("x", "X")] + [TestCase("_123", "")] + [TestCase("", "")] + [TestCase("001_CreateATable_123", "Create a table")] + public void HumanNamesHandleShortAndLowercaseNames(string name, string expected) + { + Assert.That(StringUtils.ToHumanName(name), Is.EqualTo(expected)); + } + + [Test] + public void RegisteredFactoryErrorsArePreserved() + { + var name = "Cleanup.FailingFactory"; + var failure = new InvalidOperationException("Factory configuration failed."); + ProviderFactories.RegisterFactory(name, () => throw failure); + + var actual = Assert.Throws(() => + DbProviderFactoriesHelper.GetFactory(name, "Missing.Assembly", "Missing.Factory")); + + Assert.That(actual, Is.SameAs(failure)); + } + + [Test] + public void FactoryResolutionUsesCustomThenSystemThenReflection() + { + var name = "Cleanup.FactoryPrecedence." + Guid.NewGuid(); + var assembly = typeof(SqliteFactory).Assembly.GetName().Name; + var type = typeof(SqliteFactory).FullName; + Assert.That(DbProviderFactoriesHelper.GetFactory(name, assembly, type), Is.SameAs(SqliteFactory.Instance)); + + System.Data.Common.DbProviderFactories.RegisterFactory(name, SqliteFactory.Instance); + try + { + Assert.That(DbProviderFactoriesHelper.GetFactory(name, "Missing.Assembly", "Missing.Factory"), Is.SameAs(SqliteFactory.Instance)); + var custom = new TestFactory(); + ProviderFactories.RegisterFactory(name, () => custom); + Assert.That(DbProviderFactoriesHelper.GetFactory(name, assembly, type), Is.SameAs(custom)); + } + finally + { + System.Data.Common.DbProviderFactories.UnregisterFactory(name); + } + } + + [Test] + [SetCulture("tr-TR")] + public void SqlTypeLookupDoesNotDependOnCurrentCulture() + { + var names = new TypeNames(); + names.Put(DbType.Int32, "INTEGER"); + names.Put(DbType.Int64, 20, "BIGINT"); + names.PutAlias(DbType.Int16, "SMALLINT"); + Assert.That(names.GetDbType("integer"), Is.EqualTo(DbType.Int32)); + Assert.That(names.GetDbType("bigint"), Is.EqualTo(DbType.Int64)); + Assert.That(names.GetDbType("smallint"), Is.EqualTo(DbType.Int16)); + } + + [Test] + public void DefaultTypeTemplatesExpandSizePrecisionAndScale() + { + var names = new TypeNames(); + names.Put(DbType.String, "VARCHAR($l)"); + names.Put(DbType.Decimal, "DECIMAL($p,$s)"); + names.Put(DbType.Decimal, 5, "SMALLDECIMAL($p,$s)"); + + Assert.That(names.Get(DbType.String, 80, 0, 0), Is.EqualTo("VARCHAR(80)")); + Assert.That(names.Get(DbType.Decimal, 10, 18, 4), Is.EqualTo("DECIMAL(18,4)")); + Assert.That(names.Get(DbType.Decimal, 5, 8, 2), Is.EqualTo("SMALLDECIMAL(8,2)")); + } + + private sealed class TestFactory : DbProviderFactory { } +} diff --git a/src/Migrator.Tests/FluentOperationsTests.cs b/src/Migrator.Tests/FluentOperationsTests.cs index 94e2f626..bec00573 100644 --- a/src/Migrator.Tests/FluentOperationsTests.cs +++ b/src/Migrator.Tests/FluentOperationsTests.cs @@ -21,7 +21,7 @@ [Test] public void FileAndEmbeddedScriptsUseScriptOperations() builder.Execute.Script(path); builder.Execute.EmbeddedScript(typeof(ScriptTests).Assembly, "Migrator.Tests.ScriptResource.sql"); Assert.That(builder.Build().All(x => x is ScriptOperation), Is.True); - var script = builder.Build().First().ToSql(new SqlGenerationContext(ProviderTypes.SqlServer)); + var script = builder.Build()[0].ToSql(new SqlGenerationContext(ProviderTypes.SqlServer)); Assert.That(script, Does.Contain("GO")); Assert.That(script.TrimEnd(), Does.EndWith("SELECT 2;")); } diff --git a/src/Migrator.Tests/IdentifierAndTimeRegressionTests.cs b/src/Migrator.Tests/IdentifierAndTimeRegressionTests.cs index 61668424..65cd2a97 100644 --- a/src/Migrator.Tests/IdentifierAndTimeRegressionTests.cs +++ b/src/Migrator.Tests/IdentifierAndTimeRegressionTests.cs @@ -64,7 +64,7 @@ public void TimeDefaultsAndValuesSurviveSQLiteReconstruction() using var provider = ProviderFactory.Create(ProviderTypes.SQLite, "Data Source=:memory:", null); var time = new TimeOnly(12, 34, 56).Add(TimeSpan.FromTicks(1234560)); provider.AddTable("Times", new Column("Id", DbType.Int32), new Column("Value", DbType.Time) { DefaultValue = time }); - var column = provider.GetColumns("Times").Single(c => c.Name == "Value"); + var column = provider.ReadLegacyColumns("Times").Single(c => c.Name == "Value"); Assert.That(column.Type, Is.EqualTo(DbType.Time)); Assert.That(column.DefaultValue, Is.EqualTo(time)); provider.ChangeColumn("Times", new Column("Id", DbType.Int64)); diff --git a/src/Migrator.Tests/LegacyMetadata.cs b/src/Migrator.Tests/LegacyMetadata.cs new file mode 100644 index 00000000..00c0fd41 --- /dev/null +++ b/src/Migrator.Tests/LegacyMetadata.cs @@ -0,0 +1,19 @@ +using DotNetProjects.Migrator.Framework; + +namespace Migrator.Tests; + +// These tests deliberately verify the compatibility metadata API. Keep the +// obsolete calls here so other accidental obsolete API usage still warns. +internal static class LegacyMetadata +{ +#pragma warning disable CS0618 // Explicit compatibility coverage of these three APIs. + internal static Column[] ReadLegacyColumns(this ITransformationProvider provider, string table) => + provider.GetColumns(table); + + internal static Column ReadLegacyColumn(this ITransformationProvider provider, string table, string column) => + provider.GetColumnByName(table, column); + + internal static void RemoveLegacyConstraints(this ITransformationProvider provider, string table) => + provider.RemoveAllConstraints(table); +#pragma warning restore CS0618 +} diff --git a/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs b/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs index 256ecb7e..040db5ae 100644 --- a/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs +++ b/src/Migrator.Tests/Providers/Generic/Generic_AddPrimaryKey.cs @@ -61,8 +61,8 @@ public void AddPrimaryKey_AddPrimaryKey_ShouldStillBeNotNull() Provider.AddPrimaryKey(name: "MyPkName", table: tableName, columnName1); // Assert - var column1 = Provider.GetColumnByName(table: tableName, column: columnName1); - var column2 = Provider.GetColumnByName(table: tableName, column: columnName2); + var column1 = Provider.ReadLegacyColumn(table: tableName, column: columnName1); + var column2 = Provider.ReadLegacyColumn(table: tableName, column: columnName2); Assert.That(column1.IsNullable, Is.False); Assert.That(column2.IsNullable, Is.False); diff --git a/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs index 2c494152..56d39e7b 100644 --- a/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs +++ b/src/Migrator.Tests/Providers/Generic/Generic_AddTableTestsBase.cs @@ -24,29 +24,8 @@ public void AddTable_PrimaryKeyWithIdentity_Success() new Column(column2Name,DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName, column1Name) ); // Assert - var column1 = Provider.GetColumnByName(tableName, column1Name); - var column2 = Provider.GetColumnByName(tableName, column2Name); - - Assert.That(column1.IsIdentity, Is.True); - Assert.That(column2.IsNullable, Is.False); - } - - [Test] - public void AddTable_PrimaryKeyAndIdentity_Success() - { - // Arrange - var tableName = "TableName"; - var column1Name = "Column1"; - var column2Name = "Column2"; - - // Act - Provider.AddTable(tableName, - new Column(column1Name,DbType.Int32){IsNullable = false,IsIdentity = true}, - new Column(column2Name,DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName, column1Name) ); - - // Assert - var column1 = Provider.GetColumnByName(tableName, column1Name); - var column2 = Provider.GetColumnByName(tableName, column2Name); + var column1 = Provider.ReadLegacyColumn(tableName, column1Name); + var column2 = Provider.ReadLegacyColumn(tableName, column2Name); Assert.That(column1.IsIdentity, Is.True); Assert.That(column2.IsNullable, Is.False); @@ -68,8 +47,8 @@ public void AddTable_PrimaryKeyAndIdentityWithInsertNull_Success() Provider.Insert(table: tableName, [column2Name], [999]); // Assert - var column1 = Provider.GetColumnByName(tableName, column1Name); - var column2 = Provider.GetColumnByName(tableName, column2Name); + var column1 = Provider.ReadLegacyColumn(tableName, column1Name); + var column2 = Provider.ReadLegacyColumn(tableName, column2Name); using var cmd = Provider.CreateCommand(); using var reader = Provider.Select(cmd: cmd, table: tableName, columns: [column1Name, column2Name]); @@ -87,27 +66,6 @@ public void AddTable_PrimaryKeyAndIdentityWithInsertNull_Success() Assert.That(column2.IsNullable, Is.False); } - [Test] - public void AddTable_PrimaryKeyAndIdentityWithoutNotNull_Success() - { - // Arrange - var tableName = "TableName"; - var column1Name = "Column1"; - var column2Name = "Column2"; - - // Act - Provider.AddTable(tableName, - new Column(column1Name,DbType.Int32){IsNullable = false,IsIdentity = true}, - new Column(column2Name,DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName, column1Name) ); - - // Assert - var column1 = Provider.GetColumnByName(tableName, column1Name); - var column2 = Provider.GetColumnByName(tableName, column2Name); - - Assert.That(column1.IsIdentity, Is.True); - Assert.That(column2.IsNullable, Is.False); - } - [Test] public void AddTable_NotNull_Success() { @@ -120,7 +78,7 @@ public void AddTable_NotNull_Success() new Column(column1Name,DbType.Int32){IsNullable = false} ); // Assert - var column1 = Provider.GetColumnByName(tableName, column1Name); + var column1 = Provider.ReadLegacyColumn(tableName, column1Name); Assert.That(column1.IsNullable, Is.False); } diff --git a/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs index 72f26ab3..236c055f 100644 --- a/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs +++ b/src/Migrator.Tests/Providers/Generic/Generic_ChangeColumnTestsBase.cs @@ -38,8 +38,8 @@ public void ChangeColumn_NotNullAndNullToNotNull_Success() // Assert Provider.ChangeColumn(tableName, new Column(column1Name,DbType.DateTime2){IsNullable = false}); Provider.ChangeColumn(tableName, new Column(column2Name,DbType.DateTime2){IsNullable = false}); - var column1 = Provider.GetColumnByName(tableName, column1Name); - var column2 = Provider.GetColumnByName(tableName, column2Name); + var column1 = Provider.ReadLegacyColumn(tableName, column1Name); + var column2 = Provider.ReadLegacyColumn(tableName, column2Name); Assert.That(column1.IsNullable, Is.False); Assert.That(column2.IsNullable, Is.False); diff --git a/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs b/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs index 3eb1d250..092fd3f8 100644 --- a/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs +++ b/src/Migrator.Tests/Providers/Generic/Generic_GetColumnsTestsBase.cs @@ -43,7 +43,7 @@ public void GetColumns_UniqueButNotPrimaryKey_ReturnsFalse() Provider.AddTable(tableName, new Column("Id",DbType.Int32),new DotNetProjects.Migrator.Framework.UniqueConstraint("UQ_" + tableName + "_" + "Id", "Id")); // Act - var columns = Provider.GetColumns(tableName); + var columns = Provider.ReadLegacyColumns(tableName); // Assert Assert.That(columns.Single().IsNullable, Is.True); diff --git a/src/Migrator.Tests/Providers/Generic/RawDefaultRegression.cs b/src/Migrator.Tests/Providers/Generic/RawDefaultRegression.cs index 6fe51efa..b6dd56a9 100644 --- a/src/Migrator.Tests/Providers/Generic/RawDefaultRegression.cs +++ b/src/Migrator.Tests/Providers/Generic/RawDefaultRegression.cs @@ -14,7 +14,7 @@ internal static void AssertRoundTrip(ITransformationProvider provider) provider.AddTable("RawDefaultsSource", new Column("Id", DbType.Int32), new Column("ExpressionValue", DbType.String, 50) { DefaultValue = RawSql.Insert("LOWER('ABC')") }, new Column("LiteralValue", DbType.String, 50) { DefaultValue = "LOWER('ABC')" }); - var columns = provider.GetColumns("RawDefaultsSource"); + var columns = provider.ReadLegacyColumns("RawDefaultsSource"); Assert.That(columns.Single(c => c.Name.Equals("ExpressionValue", StringComparison.OrdinalIgnoreCase)).DefaultValue, Is.TypeOf()); provider.AddTable("RawDefaultsCopy", columns); var builder = new MigrationBuilder(); diff --git a/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs index f5ce8c8d..b3ca1bdc 100644 --- a/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs +++ b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscConstraintBase.cs @@ -18,10 +18,10 @@ public void AddForeignKey() Provider.AddForeignKey("FK_Test_TestTwo", "TestTwo", "TestId", "Test", "Id"); } - public void AddPrimaryKey() + private void AddTableAndPrimaryKey() { AddTable(); - Provider.AddPrimaryKey("PK_Test", "Test", "Id"); + AddPrimaryKey(); } public void AddUniqueConstraint() @@ -42,7 +42,7 @@ public void AddTestCheckConstraint() [Test] public void CanAddPrimaryKey() { - AddPrimaryKey(); + AddTableAndPrimaryKey(); Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True); } @@ -156,7 +156,7 @@ public void AddTableWithCompoundPrimaryKeyShouldKeepNullForOtherProperties() Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist"); - var column = Provider.GetColumnByName("Test", "Name"); + var column = Provider.ReadLegacyColumn("Test", "Name"); Assert.That(column, Is.Not.Null); Assert.That(column.IsNullable, Is.True); diff --git a/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs index 3d65881d..68ff7f2f 100644 --- a/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs +++ b/src/Migrator.Tests/Providers/Generic/TransformationProviderGenericMiscTests.cs @@ -61,7 +61,7 @@ public void GetTablesWorks() public void GetColumnsReturnsProperCount() { AddTable(); - var cols = Provider.GetColumns("Test"); + var cols = Provider.ReadLegacyColumns("Test"); Assert.That(cols, Is.Not.Null); Assert.That(6, Is.EqualTo(cols.Length)); @@ -71,7 +71,7 @@ public void GetColumnsReturnsProperCount() public void GetColumnsContainsProperNullInformation() { AddTableWithPrimaryKey(); - var cols = Provider.GetColumns("Test"); + var cols = Provider.ReadLegacyColumns("Test"); Assert.That(cols, Is.Not.Null); foreach (var column in cols) @@ -211,7 +211,7 @@ public void AddBooleanColumnWithDefault() public void CanGetNullableFromProvider() { Provider.AddColumn("TestTwo", new Column("NullableColumn", DbType.String, 30)); - var columns = Provider.GetColumns("TestTwo"); + var columns = Provider.ReadLegacyColumns("TestTwo"); foreach (var column in columns) { diff --git a/src/Migrator.Tests/Providers/GenericProviderTests.cs b/src/Migrator.Tests/Providers/GenericProviderTests.cs index 9cdce313..4725aadc 100644 --- a/src/Migrator.Tests/Providers/GenericProviderTests.cs +++ b/src/Migrator.Tests/Providers/GenericProviderTests.cs @@ -10,7 +10,7 @@ public class GenericProviderTests [Test] public void CanJoinColumnsAndValues() { - var provider = new GenericTransformationProvider(); + using var provider = new GenericTransformationProvider(); var result = provider.JoinColumnsAndValues(["foo", "bar"], ["123", "456"]); Assert.That("foo='123', bar='456'", Is.EqualTo(result)); diff --git a/src/Migrator.Tests/Providers/Hana/HanaProviderTests.cs b/src/Migrator.Tests/Providers/Hana/HanaProviderTests.cs index 24aae395..516afd92 100644 --- a/src/Migrator.Tests/Providers/Hana/HanaProviderTests.cs +++ b/src/Migrator.Tests/Providers/Hana/HanaProviderTests.cs @@ -56,7 +56,7 @@ public void TimeOnlyDefaultsAndParametersAndQuotedConstraintsRoundTrip() var actual = stored is DateTime date ? TimeOnly.FromDateTime(date) : stored is TimeSpan span ? TimeOnly.FromTimeSpan(span) : TimeOnly.Parse(Convert.ToString(stored)); Assert.That(actual, Is.EqualTo(time)); } - Assert.That(provider.GetColumns("ClockValues").Single(c => c.Name == "Moment").Type, Is.EqualTo(DbType.Time)); + Assert.That(provider.ReadLegacyColumns("ClockValues").Single(c => c.Name == "Moment").Type, Is.EqualTo(DbType.Time)); provider.RemoveConstraint("ClockValues", "UQ ' dotted.name"); provider.Insert("ClockValues", ["Id"], [1]); } @@ -77,14 +77,14 @@ public void ImperativeSchemaConstraintsMetadataAndPersistedData() new CheckConstraint("CK_Label", "LENGTH(\"Label\") > 0")); provider.Insert("Items", ["Label"], ["one"]); Assert.That(Convert.ToInt32(provider.ExecuteScalar("SELECT COUNT(*) FROM \"Items\"")), Is.EqualTo(1)); - Assert.That(provider.GetColumns("Items").Single(c => c.Name == "Id").IsIdentity, Is.True); + Assert.That(provider.ReadLegacyColumns("Items").Single(c => c.Name == "Id").IsIdentity, Is.True); var constraints = provider.GetTableConstraints("Items"); Assert.That(constraints.OfType().Single().KeyColumns, Is.EqualTo(new[] { "Id" })); Assert.That(constraints.OfType().Single().Name, Is.EqualTo("UQ_Label")); Assert.That(constraints.OfType().Single().Name, Is.EqualTo("CK_Label")); Assert.Catch(() => provider.Insert("Items", ["Label"], ["one"])); Assert.Catch(() => provider.Insert("Items", ["Label"], [""])); - Assert.That(provider.GetColumns(schema + ".Items").Select(c => c.Name), Is.EqualTo(new[] { "Id", "Label" })); + Assert.That(provider.ReadLegacyColumns(schema + ".Items").Select(c => c.Name), Is.EqualTo(new[] { "Id", "Label" })); } [Test] public void FluentAndPreviewCreateEquivalentSchemasAndRawDefaults() @@ -106,7 +106,7 @@ MigrationBuilder Definition(string table) { provider.Insert(table, ["Id"], [1]); Assert.That(provider.ExecuteScalar("SELECT \"Created\" FROM \"" + table + "\""), Is.TypeOf()); - Assert.That(provider.GetColumns(table).Single(c => c.Name == "Created").DefaultValue, Is.TypeOf()); + Assert.That(provider.ReadLegacyColumns(table).Single(c => c.Name == "Created").DefaultValue, Is.TypeOf()); Assert.That(provider.GetTableConstraints(table).OfType().Single().KeyColumns, Is.EqualTo(new[] { "Id" })); } } @@ -125,7 +125,7 @@ public void AlterRenameAndIndexOperationsPreserveData() provider.ChangeColumn("Names", new Column("Label", DbType.String, 60) { IsNullable = true }); provider.Insert("Names", ["Id"], [3]); Assert.That(provider.ExecuteScalar("SELECT \"Extra\" FROM \"Names\" WHERE \"Id\"=3"), Is.EqualTo(DBNull.Value)); - Assert.That(provider.GetColumns("Names").Single(c => c.Name == "Label").IsNullable, Is.True); + Assert.That(provider.ReadLegacyColumns("Names").Single(c => c.Name == "Label").IsNullable, Is.True); provider.RenameColumn("Names", "Label", "Text"); provider.RenameTable("Names", "Renamed"); provider.AddIndex("Renamed", new Index { Name = "IX_Text", KeyColumns = ["Text"] }); diff --git a/src/Migrator.Tests/Providers/Live/LiveDatabaseTests.cs b/src/Migrator.Tests/Providers/Live/LiveDatabaseTests.cs index 8e417b51..911c4172 100644 --- a/src/Migrator.Tests/Providers/Live/LiveDatabaseTests.cs +++ b/src/Migrator.Tests/Providers/Live/LiveDatabaseTests.cs @@ -40,7 +40,7 @@ public void TimeOfDayDefaultsAndParametersPersist() var actual = stored is DateTime date ? date.TimeOfDay : stored is TimeSpan span ? span : TimeSpan.Parse(Convert.ToString(stored), System.Globalization.CultureInfo.InvariantCulture); Assert.That(actual, Is.EqualTo(time.ToTimeSpan())); } - Assert.That(provider.GetColumns("clock_values").Single(c => c.Name.Equals("value", StringComparison.OrdinalIgnoreCase)).Type, Is.EqualTo(DbType.Time)); + Assert.That(provider.ReadLegacyColumns("clock_values").Single(c => c.Name.Equals("value", StringComparison.OrdinalIgnoreCase)).Type, Is.EqualTo(DbType.Time)); if (providerType is ProviderTypes.Mysql or ProviderTypes.MariaDB) IntervalRegression.Verify(provider, false); } @@ -275,7 +275,7 @@ public void TableAndColumnMetadata() CreateItems(); Assert.That(provider.TableExists("items"), Is.True); Assert.That(provider.GetTables(), Has.Some.EqualTo("items").IgnoreCase); - var columns = provider.GetColumns("items"); + var columns = provider.ReadLegacyColumns("items"); Assert.That(columns, Has.Length.EqualTo(3)); Assert.That(columns.Single(c => c.Name.Equals("id", StringComparison.OrdinalIgnoreCase)).Type, Is.EqualTo(DbType.Int32)); Assert.That(columns.Single(c => c.Name.Equals("label", StringComparison.OrdinalIgnoreCase)).IsNullable, Is.True); @@ -321,7 +321,7 @@ public void AddRenameChangeAndDropColumn() provider.AddColumn("items", new Column("extra",DbType.String,20)); provider.RenameColumn("items", "extra", "renamed"); provider.ChangeColumn("items", new Column("renamed",DbType.String,80,"fallback"){IsNullable = false}); - var changed = provider.GetColumns("items").Single(c => c.Name.Equals("renamed", StringComparison.OrdinalIgnoreCase)); + var changed = provider.ReadLegacyColumns("items").Single(c => c.Name.Equals("renamed", StringComparison.OrdinalIgnoreCase)); Assert.That(changed.Size, Is.EqualTo(80)); Assert.That(changed.IsNullable, Is.False); provider.Insert("items", ["id"], [1]); @@ -341,7 +341,7 @@ public void PrimaryKeyAndIdentity() provider.Insert("items", ["label"], ["first"]); provider.Insert("items", ["label"], ["second"]); Assert.That(Convert.ToInt32(provider.ExecuteScalar("SELECT COUNT(DISTINCT id) FROM items")), Is.EqualTo(2)); - Assert.That(provider.GetColumns("items").Single(c => c.Name.Equals("id", StringComparison.OrdinalIgnoreCase)).IsIdentity, Is.True); + Assert.That(provider.ReadLegacyColumns("items").Single(c => c.Name.Equals("id", StringComparison.OrdinalIgnoreCase)).IsIdentity, Is.True); } [Test] diff --git a/src/Migrator.Tests/Providers/Live/LiveMetadataRegressionTests.cs b/src/Migrator.Tests/Providers/Live/LiveMetadataRegressionTests.cs index 3b132829..8e13bfd5 100644 --- a/src/Migrator.Tests/Providers/Live/LiveMetadataRegressionTests.cs +++ b/src/Migrator.Tests/Providers/Live/LiveMetadataRegressionTests.cs @@ -33,11 +33,11 @@ public class LiveMetadataRegressionTests public void SybaseLargeTextMetadataPreservesCapacity() => new LiveDatabaseTests("Sybase", ProviderTypes.Sybase).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (text_value TEXT NULL, unicode_value UNITEXT NULL)"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Select(c => c.Type), Is.EqualTo(new[] { DbType.String, DbType.String })); Assert.That(columns.Select(c => c.Size), Is.EqualTo(new[] { int.MaxValue, int.MaxValue })); f.Provider.AddTable("copied_values", columns); - Assert.That(f.Provider.GetColumns("copied_values").Select(c => c.Size), Is.EqualTo(new[] { int.MaxValue, int.MaxValue })); + Assert.That(f.Provider.ReadLegacyColumns("copied_values").Select(c => c.Size), Is.EqualTo(new[] { int.MaxValue, int.MaxValue })); var content = new string('x', 5000); f.Provider.Insert("copied_values", ["text_value", "unicode_value"], [content, content]); Assert.That(f.Provider.ExecuteScalar("SELECT text_value FROM copied_values"), Is.EqualTo(content)); @@ -48,7 +48,7 @@ public class LiveMetadataRegressionTests public void InformixTypedDefaultsSurviveMetadataCopy() => new LiveDatabaseTests("Informix", ProviderTypes.IBM_Informix).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (id INTEGER, amount INTEGER DEFAULT 7, price DECIMAL(12,3) DEFAULT 12.345, enabled BOOLEAN DEFAULT 't', disabled BOOLEAN DEFAULT 'f', label VARCHAR(40) DEFAULT ' O''Brien ', stamp DATETIME YEAR TO FRACTION(5) DEFAULT CURRENT YEAR TO FRACTION(5), today_value DATE DEFAULT TODAY, null_value INTEGER DEFAULT NULL)"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); foreach (var column in columns) TestContext.WriteLine($"Default {column.Name}: [{column.DefaultValue}] ({column.DefaultValue?.GetType().Name})"); Assert.That(columns.Single(c => c.Name == "amount").DefaultValue, Is.TypeOf().And.EqualTo(7)); @@ -92,16 +92,16 @@ public class LiveMetadataRegressionTests public void InformixLargeTextMetadataCopiesAsLargeObjects() => new LiveDatabaseTests("Informix", ProviderTypes.IBM_Informix).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (text_value TEXT, clob_value CLOB)"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Select(c => c.Type), Is.EqualTo(new[] { DbType.String, DbType.String })); Assert.That(columns.Select(c => c.Size), Is.EqualTo(new[] { int.MaxValue, int.MaxValue })); f.Provider.AddTable("copied_values", columns); - Assert.That(f.Provider.GetColumns("copied_values").Select(c => c.Size), Is.EqualTo(new[] { int.MaxValue, int.MaxValue })); + Assert.That(f.Provider.ReadLegacyColumns("copied_values").Select(c => c.Size), Is.EqualTo(new[] { int.MaxValue, int.MaxValue })); // A maximum-width LVARCHAR leaves insufficient row space for an additional LOB locator. f.Provider.AddTable("bounded_values", new Column("bounded_value", DbType.String, 32739)); f.Provider.AddTable("large_values", new Column("large_value", DbType.AnsiString, int.MaxValue)); - Assert.That(f.Provider.GetColumns("bounded_values").Single().Size, Is.EqualTo(32739)); - Assert.That(f.Provider.GetColumns("large_values").Single().Size, Is.EqualTo(int.MaxValue)); + Assert.That(f.Provider.ReadLegacyColumns("bounded_values").Single().Size, Is.EqualTo(32739)); + Assert.That(f.Provider.ReadLegacyColumns("large_values").Single().Size, Is.EqualTo(int.MaxValue)); var content = new string('z', 40000); f.Provider.Insert("copied_values", ["text_value", "clob_value"], [content, content]); Assert.That(f.Provider.ExecuteScalar("SELECT text_value FROM copied_values"), Is.EqualTo(content)); @@ -112,12 +112,12 @@ public class LiveMetadataRegressionTests public void InformixCharacterLengthsSurviveMetadataCopy() => new LiveDatabaseTests("Informix", ProviderTypes.IBM_Informix).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (long_text LVARCHAR(3000), short_text VARCHAR(40,10), fixed_text CHAR(300))"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Select(c => c.Size), Is.EqualTo(new[] { 3000, 40, 300 })); Assert.That(columns.Select(c => c.Type), Is.EqualTo(new[] { DbType.String, DbType.String, DbType.StringFixedLength })); f.Provider.AddTable("copied_values", columns); - Assert.That(f.Provider.GetColumns("copied_values").Select(c => c.Size), Is.EqualTo(new[] { 3000, 40, 300 })); - Assert.That(f.Provider.GetColumns("copied_values").Last().Type, Is.EqualTo(DbType.StringFixedLength)); + Assert.That(f.Provider.ReadLegacyColumns("copied_values").Select(c => c.Size), Is.EqualTo(new[] { 3000, 40, 300 })); + Assert.That(f.Provider.ReadLegacyColumns("copied_values").Last().Type, Is.EqualTo(DbType.StringFixedLength)); var content = new string('x', 2500); f.Provider.Insert("copied_values", ["long_text", "short_text", "fixed_text"], [content, "short", new string('y', 300)]); Assert.That(f.Provider.ExecuteScalar("SELECT long_text FROM copied_values"), Is.EqualTo(content)); @@ -128,10 +128,10 @@ public class LiveMetadataRegressionTests public void FirebirdNativeDateTimeAndBooleanSurviveMetadataCopy() => new LiveDatabaseTests("Firebird", ProviderTypes.Firebird).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (date_value DATE, time_value TIME, enabled BOOLEAN)"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Select(c => c.Type), Is.EqualTo(new[] { DbType.Date, DbType.Time, DbType.Boolean })); f.Provider.AddTable("copied_values", columns); - Assert.That(f.Provider.GetColumns("copied_values").Select(c => c.Type), Is.EqualTo(new[] { DbType.Date, DbType.Time, DbType.Boolean })); + Assert.That(f.Provider.ReadLegacyColumns("copied_values").Select(c => c.Type), Is.EqualTo(new[] { DbType.Date, DbType.Time, DbType.Boolean })); f.Provider.ExecuteNonQuery("INSERT INTO copied_values VALUES (DATE '2026-09-22', TIME '12:34:56', TRUE)"); Assert.That(Convert.ToInt32(f.Provider.ExecuteScalar("SELECT COUNT(*) FROM copied_values WHERE date_value = DATE '2026-09-22' AND time_value = TIME '12:34:56' AND enabled IS TRUE")), Is.EqualTo(1)); }); @@ -143,21 +143,21 @@ public class LiveMetadataRegressionTests public void DecimalShapeSurvivesCreateAlterAndCopy(string database, ProviderTypes type) => new LiveDatabaseTests(database, type).RunRegression(f => { f.Provider.AddTable("numbers", new Column("amount",DbType.Decimal){Precision = 12,Scale = 3 }); - var original = f.Provider.GetColumns("numbers").Single(); + var original = f.Provider.ReadLegacyColumns("numbers").Single(); Assert.That(original.Precision, Is.EqualTo(12)); Assert.That(original.Scale, Is.EqualTo(3)); f.Provider.Insert("numbers", ["amount"], [123.456m]); f.Provider.ChangeColumn("numbers", new Column("amount",DbType.Decimal){Precision = 15,Scale = 3 }); - var changed = f.Provider.GetColumns("numbers").Single(); + var changed = f.Provider.ReadLegacyColumns("numbers").Single(); Assert.That(changed.Precision, Is.EqualTo(15)); Assert.That(changed.Scale, Is.EqualTo(3)); Assert.That(Convert.ToDecimal(f.Provider.ExecuteScalar("SELECT amount FROM numbers")), Is.EqualTo(123.456m)); f.Provider.AddTable("copied_numbers", changed); - var copied = f.Provider.GetColumns("copied_numbers").Single(); + var copied = f.Provider.ReadLegacyColumns("copied_numbers").Single(); Assert.That(copied.Precision, Is.EqualTo(15)); Assert.That(copied.Scale, Is.EqualTo(3)); f.Provider.AddColumn("copied_numbers", new Column("extra",DbType.Decimal){Precision = 10,Scale = 2 }); - var added = f.Provider.GetColumns("copied_numbers").Single(c => c.Name.Equals("extra", StringComparison.OrdinalIgnoreCase)); + var added = f.Provider.ReadLegacyColumns("copied_numbers").Single(c => c.Name.Equals("extra", StringComparison.OrdinalIgnoreCase)); Assert.That(added.Precision, Is.EqualTo(10)); Assert.That(added.Scale, Is.EqualTo(2)); }); @@ -168,9 +168,9 @@ public class LiveMetadataRegressionTests public void PrimaryKeyMetadataIncludesIdentityAndCompositeMembers(string database, ProviderTypes type) => new LiveDatabaseTests(database, type).RunRegression(f => { f.Provider.AddTable("identities", new Column("id",DbType.Int32){IsNullable = false,IsIdentity = true},new PrimaryKeyConstraint("PK_" + "identities", "id")); - Assert.That(f.Provider.GetColumns("identities").Single().IsIdentity, Is.True); + Assert.That(f.Provider.ReadLegacyColumns("identities").Single().IsIdentity, Is.True); f.Provider.AddTable("pairs", new Column("first_id",DbType.Int32){IsNullable = false}, new Column("second_id",DbType.Int32){IsNullable = false}, new Column("label", DbType.String, 20),new PrimaryKeyConstraint("PK_" + "pairs", "first_id", "second_id")); - var columns = f.Provider.GetColumns("pairs"); + var columns = f.Provider.ReadLegacyColumns("pairs"); Assert.That(f.Provider.GetTableConstraints("pairs").OfType().Single().KeyColumns.Length, Is.EqualTo(2)); Assert.That(f.Provider.GetTableConstraints("pairs").OfType().Single().KeyColumns.Any(c => c.Equals("label", StringComparison.OrdinalIgnoreCase)), Is.False); }); @@ -179,11 +179,11 @@ public class LiveMetadataRegressionTests public void Db2DecfloatPrecisionRoundTrips() => new LiveDatabaseTests("Db2", ProviderTypes.IBM_DB2).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE floats (small_value DECFLOAT(16), large_value DECFLOAT(34))"); - var columns = f.Provider.GetColumns("floats"); + var columns = f.Provider.ReadLegacyColumns("floats"); Assert.That(columns.Select(c => c.Type), Is.All.EqualTo(DbType.VarNumeric)); Assert.That(columns.Select(c => c.Precision), Is.EqualTo(new int?[] { 16, 34 })); f.Provider.AddTable("copied_floats", columns); - Assert.That(f.Provider.GetColumns("copied_floats").Select(c => c.Precision), Is.EqualTo(new int?[] { 16, 34 })); + Assert.That(f.Provider.ReadLegacyColumns("copied_floats").Select(c => c.Precision), Is.EqualTo(new int?[] { 16, 34 })); }); [TestCase("Db2", ProviderTypes.IBM_DB2, Category = "Db2")] @@ -193,7 +193,7 @@ public class LiveMetadataRegressionTests var boolean = database == "Db2" ? "BOOLEAN DEFAULT TRUE" : "BIT DEFAULT 1"; var timestamp = database == "Db2" ? "TIMESTAMP DEFAULT CURRENT TIMESTAMP" : "DATETIME DEFAULT GETDATE()"; f.Provider.ExecuteNonQuery($"CREATE TABLE source_values (id INTEGER, amount INTEGER DEFAULT 7, enabled {boolean}, label VARCHAR(40) DEFAULT 'O''Brien', stamp {timestamp})"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Single(c => c.Name.Equals("amount", StringComparison.OrdinalIgnoreCase)).DefaultValue, Is.TypeOf().And.EqualTo(7)); Assert.That(columns.Single(c => c.Name.Equals("enabled", StringComparison.OrdinalIgnoreCase)).DefaultValue, Is.TypeOf().And.EqualTo(true)); Assert.That(columns.Single(c => c.Name.Equals("label", StringComparison.OrdinalIgnoreCase)).DefaultValue, Is.EqualTo("O'Brien")); @@ -208,7 +208,7 @@ public class LiveMetadataRegressionTests public void FirebirdTextAndBinaryBlobsRoundTrip() => new LiveDatabaseTests("Firebird", ProviderTypes.Firebird).RunRegression(f => { f.Provider.AddTable("large_values", new Column("contents", DbType.String, int.MaxValue), new Column("binary_value", DbType.Binary)); - var columns = f.Provider.GetColumns("large_values"); + var columns = f.Provider.ReadLegacyColumns("large_values"); Assert.That(columns[0].Type, Is.EqualTo(DbType.String)); Assert.That(columns[0].Size, Is.EqualTo(int.MaxValue)); Assert.That(columns[1].Type, Is.EqualTo(DbType.Binary)); @@ -224,7 +224,7 @@ public class LiveMetadataRegressionTests public void MySqlBlobVariantsRemainBinary(string database, ProviderTypes type) => new LiveDatabaseTests(database, type).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE blobs (tiny_value TINYBLOB, medium_value MEDIUMBLOB, ordinary_value BLOB, large_value LONGBLOB)"); - var columns = f.Provider.GetColumns("blobs"); + var columns = f.Provider.ReadLegacyColumns("blobs"); Assert.That(columns.Select(c => c.Type), Is.All.EqualTo(DbType.Binary)); f.Provider.AddTable("copied_blobs", columns); f.Provider.Insert("copied_blobs", ["tiny_value", "medium_value"], [new byte[] { 0, 255 }, new byte[] { 1, 255 }]); @@ -236,10 +236,10 @@ public class LiveMetadataRegressionTests public void InformixTimeMetadataRoundTrips() => new LiveDatabaseTests("Informix", ProviderTypes.IBM_Informix).RunRegression(f => { f.Provider.AddTable("times", new Column("time_value", DbType.Time)); - var column = f.Provider.GetColumns("times").Single(); + var column = f.Provider.ReadLegacyColumns("times").Single(); Assert.That(column.Type, Is.EqualTo(DbType.Time)); f.Provider.AddTable("copied_times", column); - Assert.That(f.Provider.GetColumns("copied_times").Single().Type, Is.EqualTo(DbType.Time)); + Assert.That(f.Provider.ReadLegacyColumns("copied_times").Single().Type, Is.EqualTo(DbType.Time)); f.Provider.ExecuteNonQuery("INSERT INTO copied_times VALUES (DATETIME(12:34:56) HOUR TO SECOND)"); Assert.That(Convert.ToInt32(f.Provider.ExecuteScalar("SELECT COUNT(*) FROM copied_times WHERE time_value=DATETIME(12:34:56) HOUR TO SECOND")), Is.EqualTo(1)); }); @@ -248,7 +248,7 @@ public class LiveMetadataRegressionTests public void SybaseByteMetadataRoundTrips() => new LiveDatabaseTests("Sybase", ProviderTypes.Sybase).RunRegression(f => { f.Provider.AddTable("bytes", new Column("byte_value", DbType.Byte)); - var column = f.Provider.GetColumns("bytes").Single(); + var column = f.Provider.ReadLegacyColumns("bytes").Single(); Assert.That(column.Type, Is.EqualTo(DbType.Byte)); f.Provider.AddTable("copied_bytes", column); f.Provider.Insert("copied_bytes", ["byte_value"], [(byte)255]); @@ -279,7 +279,7 @@ public class LiveMetadataRegressionTests public void FirebirdDefaultsRoundTrip() => new LiveDatabaseTests("Firebird", ProviderTypes.Firebird).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (amount INTEGER DEFAULT 7, label VARCHAR(40) DEFAULT 'O''Brien', stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Single(c => c.Name == "AMOUNT").DefaultValue, Is.TypeOf().And.EqualTo(7)); Assert.That(columns.Single(c => c.Name == "LABEL").DefaultValue, Is.EqualTo("O'Brien")); f.Provider.AddTable("copied_values", columns); @@ -293,7 +293,7 @@ public class LiveMetadataRegressionTests public void FirebirdDecimalPrecisionAndScale() => new LiveDatabaseTests("Firebird", ProviderTypes.Firebird).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE numbers (amount DECIMAL(12,3))"); - var column = f.Provider.GetColumns("numbers").Single(); + var column = f.Provider.ReadLegacyColumns("numbers").Single(); Assert.That(column.Type, Is.EqualTo(DbType.Decimal)); Assert.That(column.Precision, Is.EqualTo(12)); Assert.That(column.Scale, Is.EqualTo(3)); @@ -334,7 +334,7 @@ public class LiveMetadataRegressionTests { f.Provider.ExecuteNonQuery("CREATE TABLE \"MixedCase\" (id INTEGER)"); Assert.That(f.Provider.TableExists("\"MixedCase\""), Is.True); - Assert.That(f.Provider.GetColumns("\"MixedCase\"").Single().Name, Is.EqualTo("id")); + Assert.That(f.Provider.ReadLegacyColumns("\"MixedCase\"").Single().Name, Is.EqualTo("id")); }); [TestCase("MySQL", ProviderTypes.Mysql, Category = "MySQL")] @@ -342,7 +342,7 @@ public class LiveMetadataRegressionTests public void MySqlDefaultsAndBooleanMetadataRoundTrip(string database, ProviderTypes type) => new LiveDatabaseTests(database, type).RunRegression(f => { f.Provider.ExecuteNonQuery("CREATE TABLE source_values (enabled TINYINT(1) DEFAULT 1, amount INTEGER DEFAULT 7, label VARCHAR(40) DEFAULT 'O''Brien', stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"); - var columns = f.Provider.GetColumns("source_values"); + var columns = f.Provider.ReadLegacyColumns("source_values"); Assert.That(columns.Single(c => c.Name == "enabled").Type, Is.EqualTo(DbType.Boolean)); Assert.That(columns.Single(c => c.Name == "enabled").DefaultValue, Is.EqualTo(true)); Assert.That(columns.Single(c => c.Name == "amount").DefaultValue, Is.TypeOf().And.EqualTo(7)); diff --git a/src/Migrator.Tests/Providers/MySQL/MySqlTransformationProviderTest.cs b/src/Migrator.Tests/Providers/MySQL/MySqlTransformationProviderTest.cs deleted file mode 100644 index e38837f9..00000000 --- a/src/Migrator.Tests/Providers/MySQL/MySqlTransformationProviderTest.cs +++ /dev/null @@ -1,61 +0,0 @@ -// using System; -// using System.Data; -// using Migrator.Framework; -// using Migrator.Providers; -// using Migrator.Providers.Mysql; -// using Migrator.Tests.Settings; -// using Migrator.Tests.Settings.Config; -// using NUnit.Framework; - -// namespace Migrator.Tests.Providers.MySQL; - -// [TestFixture] -// [Category("MySQL")] -// public class MySqlTransformationProviderTest : TransformationProviderConstraintBase -// { -// [SetUp] -// public void SetUp() -// { -// var configReader = new ConfigurationReader(); -// var connectionString = configReader.GetDatabaseConnectionConfigById(DatabaseConnectionConfigIds.MySQLId) -// ?.ConnectionString; - -// if (string.IsNullOrEmpty(connectionString)) -// { -// throw new IgnoreException("No MySQL ConnectionString is Set."); -// } - -// DbProviderFactories.RegisterFactory("MySql.Data.MySqlClient", () => MySql.Data.MySqlClient.MySqlClientFactory.Instance); - -// Provider = new MySqlTransformationProvider(new MysqlDialect(), connectionString, "default", null); - -// AddDefaultTable(); -// } - -// [TearDown] -// public override void TearDown() -// { -// DropTestTables(); -// } - -// // [Test,Ignore("MySql doesn't support check constraints")] -// public override void CanAddCheckConstraint() -// { -// } - -// [Test] -// public void AddTableWithMyISAMEngine() -// { -// Provider.AddTable("Test", "MyISAM", -// new Column("Id", DbType.Int32, ColumnProperty.NotNull), -// new Column("name", DbType.String, 50) -// ); -// } - -// [Test] -// [Ignore("needs to be fixed")] -// public override void RemoveForeignKey() -// { -// //Foreign Key exists method seems not to return the key, but the ConstraintExists does -// } -// } diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs index 6b652d92..cfc1a2a8 100644 --- a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_AddColumnTests.cs @@ -33,8 +33,8 @@ public void AddTable_NotNull_OtherColumnStillNotNull() // Assert - var column1 = Provider.GetColumnByName(tableName, column1Name); - var column2 = Provider.GetColumnByName(tableName, column2Name); + var column1 = Provider.ReadLegacyColumn(tableName, column1Name); + var column2 = Provider.ReadLegacyColumn(tableName, column2Name); Assert.That(column1.IsNullable, Is.False); Assert.That(column2.IsNullable, Is.False); diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs index 2bd2e1e9..7bc4370a 100644 --- a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_GetColumns_Tests.cs @@ -35,7 +35,7 @@ public void GetColumns_Oracle_DefaultValues_Succeeds() ); // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); // Assert var binarycolumn1 = columns.Single(x => x.Name.Equals(binaryColumnName1, StringComparison.OrdinalIgnoreCase)); @@ -84,7 +84,7 @@ public void GetColumns_DefaultValues_Succeeds() ); // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); // Assert var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase)); @@ -128,10 +128,10 @@ public void GetColumns_GetIdentity_Succeeds() Provider.AddTable(name: tableName4, new Column(columnName1,DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName4, columnName1)); // Act - var columnTable1 = Provider.GetColumnByName(table: tableName1, column: columnName1); - var columnTable2 = Provider.GetColumnByName(table: tableName2, column: columnName1); - var columnTable3 = Provider.GetColumnByName(table: tableName3, column: columnName1); - var columnTable4 = Provider.GetColumnByName(table: tableName4, column: columnName1); + var columnTable1 = Provider.ReadLegacyColumn(table: tableName1, column: columnName1); + var columnTable2 = Provider.ReadLegacyColumn(table: tableName2, column: columnName1); + var columnTable3 = Provider.ReadLegacyColumn(table: tableName3, column: columnName1); + var columnTable4 = Provider.ReadLegacyColumn(table: tableName4, column: columnName1); // Assert Assert.That(columnTable1.IsIdentity, Is.True); diff --git a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs index 3ed399ba..2ce64698 100644 --- a/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs +++ b/src/Migrator.Tests/Providers/OracleProvider/OracleTransformationProvider_TableExistsTests.cs @@ -23,7 +23,7 @@ public void QualifiedQuotedNamesRoundTripThroughColumnsIndexesAndConstraints() new DotNetProjects.Migrator.Framework.UniqueConstraint("UQ ' name", "Id")); Assert.That(Provider.TableExists(table), Is.True); Assert.That(Provider.ColumnExists(table, "Id"), Is.True); - Assert.That(Provider.GetColumns(table).Length, Is.EqualTo(1)); + Assert.That(Provider.ReadLegacyColumns(table).Length, Is.EqualTo(1)); Assert.That(Provider.GetIndexes(table).Length, Is.EqualTo(1)); Assert.That(Provider.GetTableConstraints(table).Length, Is.EqualTo(1)); Provider.RemoveConstraint(table, "UQ ' name"); diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs index b8f92b1a..1e0503a3 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs @@ -36,7 +36,7 @@ public void ChangeColumn_DateTimeOffsetToDateTime_Success() // Assert Provider.ChangeColumn(tableName, new Column(column2Name,DbType.DateTime2){IsNullable = false}); - var column2 = Provider.GetColumnByName(tableName, column2Name); + var column2 = Provider.ReadLegacyColumn(tableName, column2Name); Assert.That(column2.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime2)); Assert.That(column2.DefaultValue, Is.Null); @@ -59,13 +59,13 @@ public void ChangeColumn_DateTimeOffsetToDateTimeGetDefaultValueAndReuseIt_Defau Provider.Insert(table: tableName, columns: [column2Name], values: [dateTimeOffsetInsert]); // Act - var column2 = Provider.GetColumnByName(tableName, column2Name); + var column2 = Provider.ReadLegacyColumn(tableName, column2Name); Assert.That(((DateTimeOffset)column2.DefaultValue).UtcDateTime, Is.EqualTo(dateTimeOffsetDefaultValue.UtcDateTime)); Provider.ChangeColumn(tableName, new Column(column2Name,DbType.DateTime2,defaultValue: column2.DefaultValue){IsNullable = false}); // Assert - column2 = Provider.GetColumnByName(tableName, column2Name); + column2 = Provider.ReadLegacyColumn(tableName, column2Name); // using var reader = Provider.Select(Provider.GetCommand(), what: column2Name, from: tableName); // var valueFromDatabase = reader.GetDateTime(0); @@ -91,10 +91,10 @@ public void GetColumns_GetIdentity_Succeeds() Provider.AddTable(name: tableName4, new Column(columnName1,DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName4, columnName1)); // Act - var columnTable1 = Provider.GetColumnByName(table: tableName1, column: columnName1); - var columnTable2 = Provider.GetColumnByName(table: tableName2, column: columnName1); - var columnTable3 = Provider.GetColumnByName(table: tableName3, column: columnName1); - var columnTable4 = Provider.GetColumnByName(table: tableName4, column: columnName1); + var columnTable1 = Provider.ReadLegacyColumn(table: tableName1, column: columnName1); + var columnTable2 = Provider.ReadLegacyColumn(table: tableName2, column: columnName1); + var columnTable3 = Provider.ReadLegacyColumn(table: tableName3, column: columnName1); + var columnTable4 = Provider.ReadLegacyColumn(table: tableName4, column: columnName1); // Assert Assert.That(columnTable1.IsIdentity, Is.True); diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs index fac5efa9..7bdfa024 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -46,7 +46,7 @@ public void GetColumns_DataTypeResolveSucceeds() // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs index c6510aec..770ec0ee 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs @@ -38,7 +38,7 @@ public void GetColumns_Postgres_DefaultValues_Succeeds() ); // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); // Assert var intervalColumn1 = columns.Single(x => x.Name == intervalColumnName1); @@ -97,7 +97,7 @@ public void GetColumns_DefaultValues_Succeeds() ); // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); // Assert var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase)); @@ -159,7 +159,7 @@ public void GetColumns_DefaultValueBooleanValues_Succeeds(object inboundBooleanD ); // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); // Assert var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_MetadataScopeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_MetadataScopeTests.cs index b58eaca8..ea539ecb 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_MetadataScopeTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_MetadataScopeTests.cs @@ -27,9 +27,9 @@ public void QualifiedMetadataDoesNotMixSameNamedTablesOrConstraints() Assert.That(Provider.ConstraintExists("metadata_a.sample", "same_name"), Is.True); Assert.That(Provider.ConstraintExists("metadata_b.sample", "same_name"), Is.False); Assert.That(Provider.GetTableConstraints("metadata_a.sample").OfType().Any(), Is.True); - Assert.That(Provider.GetColumns("metadata_b.sample").Single().MigratorDbType, Is.EqualTo(MigratorDbType.String)); + Assert.That(Provider.ReadLegacyColumns("metadata_b.sample").Single().MigratorDbType, Is.EqualTo(MigratorDbType.String)); Provider.ExecuteNonQuery("SET LOCAL search_path TO metadata_b"); - Assert.That(Provider.GetColumns("sample").Single().Name, Is.EqualTo("value")); + Assert.That(Provider.ReadLegacyColumns("sample").Single().Name, Is.EqualTo("value")); } [Test] @@ -46,7 +46,7 @@ public void NativeTimeRoundTripsThroughMetadataDefaultsAndParameters() { var value = new TimeOnly(12, 34, 56, 789); Provider.AddTable("NativeTimeRoundTrip", new Column("Value", DbType.Time, value)); - var column = Provider.GetColumns("NativeTimeRoundTrip").Single(); + var column = Provider.ReadLegacyColumns("NativeTimeRoundTrip").Single(); Assert.That(column.MigratorDbType, Is.EqualTo(MigratorDbType.Time)); Assert.That(column.DefaultValue, Is.EqualTo(value)); Provider.Insert("NativeTimeRoundTrip", new[] { "Value" }, new object[] { value }); diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs index 5ff9d5fd..079c179d 100644 --- a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_AddTableTests.cs @@ -16,17 +16,6 @@ public async Task SetUpAsync() await BeginSQLServerTransactionAsync(); } - [Test] - public void AddTableWithCompoundPrimaryKey() - { - Provider.AddTable("Test", - new Column("PersonId",DbType.Int32){IsNullable = false}, - new Column("AddressId",DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + "Test", "PersonId", "AddressId") ); - - Assert.That(Provider.TableExists("Test"), Is.True, "Table doesn't exist"); - Assert.That(Provider.PrimaryKeyExists("Test", "PK_Test"), Is.True, "Constraint doesn't exist"); - } - [Test] public void AddTableDateTime() { @@ -34,7 +23,7 @@ public void AddTableDateTime() var columnName = "Column1"; Provider.AddTable(tableName, new Column(columnName,DbType.DateTime){IsNullable = false}); - var column = Provider.GetColumnByName(tableName, columnName); + var column = Provider.ReadLegacyColumn(tableName, columnName); Assert.That(column.Type, Is.EqualTo(DbType.DateTime)); } @@ -46,7 +35,7 @@ public void AddTableDateTime2() var columnName = "Column1"; Provider.AddTable(tableName, new Column(columnName,DbType.DateTime2){IsNullable = false}); - var column = Provider.GetColumnByName(tableName, columnName); + var column = Provider.ReadLegacyColumn(tableName, columnName); Assert.That(column.Type, Is.EqualTo(DbType.DateTime2)); } diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs index 9162fc95..e18fef9a 100644 --- a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ChangeColumnTests.cs @@ -56,13 +56,13 @@ public void ChangeColumn_DateTimeToDateTime2_Success() const string columnName = "TestColumn"; Provider.AddTable(tableName, new Column(columnName,DbType.DateTime){IsNullable = false}); - var columnBefore = Provider.GetColumnByName(tableName, columnName); + var columnBefore = Provider.ReadLegacyColumn(tableName, columnName); // Act Provider.ChangeColumn(tableName, new Column(columnName,DbType.DateTime2){IsNullable = false}); // Assert - var columnAfter = Provider.GetColumnByName(tableName, columnName); + var columnAfter = Provider.ReadLegacyColumn(tableName, columnName); Assert.That(columnBefore.Type == DbType.DateTime); Assert.That(columnAfter.Type == DbType.DateTime2); diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs index f42f0fb2..8746359d 100644 --- a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_ConstraintExistsTests.cs @@ -16,7 +16,7 @@ public void QualifiedNamesKeepColumnsIndexesAndConstraintsInTheirSchema() Provider.AddTable(table, new DotNetProjects.Migrator.Framework.Column("Id", System.Data.DbType.Int32), new DotNetProjects.Migrator.Framework.UniqueConstraint("UQ ' name", "Id")); Assert.That(Provider.TableExists(table), Is.True); - Assert.That(Provider.GetColumns(table).Length, Is.EqualTo(1)); + Assert.That(Provider.ReadLegacyColumns(table).Length, Is.EqualTo(1)); Assert.That(Provider.GetIndexes(table).Length, Is.EqualTo(1)); Assert.That(Provider.ConstraintExists(table, "UQ ' name"), Is.True); Provider.RemoveConstraint(table, "UQ ' name"); diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs index 50987586..63acb70e 100644 --- a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumnsTests.cs @@ -33,10 +33,10 @@ public void GetColumns_GetIdentity_Succeeds() Provider.AddTable(name: tableName4, new Column(columnName1,DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName4, columnName1)); // Act - var columnTable1 = Provider.GetColumnByName(table: tableName1, column: columnName1); - var columnTable2 = Provider.GetColumnByName(table: tableName2, column: columnName1); - var columnTable3 = Provider.GetColumnByName(table: tableName3, column: columnName1); - var columnTable4 = Provider.GetColumnByName(table: tableName4, column: columnName1); + var columnTable1 = Provider.ReadLegacyColumn(table: tableName1, column: columnName1); + var columnTable2 = Provider.ReadLegacyColumn(table: tableName2, column: columnName1); + var columnTable3 = Provider.ReadLegacyColumn(table: tableName3, column: columnName1); + var columnTable4 = Provider.ReadLegacyColumn(table: tableName4, column: columnName1); // Assert Assert.That(columnTable1.IsIdentity, Is.True); diff --git a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs index b138b562..5cd551f5 100644 --- a/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs +++ b/src/Migrator.Tests/Providers/SQLServer/SQLServerTransformationProvider_GetColumns_DefaultValues_Tests.cs @@ -63,7 +63,7 @@ public void GetColumns_DefaultValues_Succeeds() ); // Act - var columns = Provider.GetColumns(testTableName); + var columns = Provider.ReadLegacyColumns(testTableName); // Assert var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs b/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs index 3bfd5384..dffdfb41 100644 --- a/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs +++ b/src/Migrator.Tests/Providers/SQLServer/SqlServerTransformationProviderTests.cs @@ -42,7 +42,7 @@ public void TimeTypeDefaultAndValueRoundTripThroughMetadata() { var time = new TimeOnly(12, 34, 56, 789); Provider.AddTable("ClockValues", new Column("Moment",DbType.Time,time)); - var column = Provider.GetColumns("ClockValues").Single(); + var column = Provider.ReadLegacyColumns("ClockValues").Single(); Assert.That(column.Type, Is.EqualTo(DbType.Time)); Assert.That(column.DefaultValue, Is.EqualTo(time)); Provider.AddTable("CopiedClock", column); diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs index 0e0d05ad..be1fb26a 100644 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_AddTableTests.cs @@ -112,7 +112,7 @@ public void AddTable_MiscellaneousColumns_Succeeds() // Assert var createScript = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(tableName); - Assert.That(Provider.GetColumns(tableName).Single(c => c.Name == columnName1).IsIdentity, Is.True); + Assert.That(Provider.ReadLegacyColumns(tableName).Single(c => c.Name == columnName1).IsIdentity, Is.True); Assert.That(Provider.GetTableConstraints(tableName).OfType().Single().KeyColumns, Is.EqualTo(new[] { columnName2 })); var pragmaTableInfos = ((SQLiteTransformationProvider)Provider).GetPragmaTableInfoItems(tableName); diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs index ec37978e..1e611b84 100644 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ChangeColumnTests.cs @@ -44,7 +44,7 @@ public void ChangeColumn_HavingColumnPropertyUniqueAndIndex_RebuildSucceeds() // Assert var createScriptAfter = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); - Assert.That(Provider.GetColumns(testTableName).Single(c => c.Name == propertyName2).IsNullable, Is.True); + Assert.That(Provider.ReadLegacyColumns(testTableName).Single(c => c.Name == propertyName2).IsNullable, Is.True); using var command = Provider.GetCommand(); using var reader = Provider.ExecuteQuery(command, $"SELECT COUNT(*) as Count from {testTableName}"); diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ConstraintExistsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ConstraintExistsTests.cs deleted file mode 100644 index a53f6f8e..00000000 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_ConstraintExistsTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; -using Migrator.Tests.Providers.SQLite.Base; -using NUnit.Framework; - -namespace Migrator.Tests.Providers.SQLite; - -[TestFixture] -[Category("SQLite")] -public class SQLiteTransformationProvider_ConstraintExistsTests : SQLiteTransformationProviderTestBase -{ - [SetUp] - public async Task SetUpAsync() - { - await BeginSQLiteTransactionAsync(); - } -} diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs index 443519ac..1b5b8486 100644 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_GetColumnsTests.cs @@ -26,7 +26,7 @@ public void GetColumns_PrimaryAndUnique_ReturnsFalse() Provider.AddTable(tableName, new Column("Id",DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName, "Id"),new DotNetProjects.Migrator.Framework.UniqueConstraint("UQ_" + tableName + "_" + "Id", "Id")); // Act - var columns = Provider.GetColumns(tableName); + var columns = Provider.ReadLegacyColumns(tableName); // Assert Assert.That(columns.Single().IsNullable, Is.False); @@ -40,10 +40,10 @@ public void GetColumns_Primary_ColumnPropertyOk() // Arrange const string tableName = "GetColumnsTest"; Provider.AddTable(tableName, new Column("Id",DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName, "Id")); - Provider.GetColumns(tableName); + Provider.ReadLegacyColumns(tableName); // Act - var columns = Provider.GetColumns(tableName); + var columns = Provider.ReadLegacyColumns(tableName); // Assert Assert.That(columns.Single().IsNullable, Is.False); @@ -61,7 +61,7 @@ public void GetColumns_PrimaryKeyOnTwoColumns_BothColumnsHavePrimaryKeyAndAreNot new Column("Id2",DbType.Int32){IsNullable = false},new PrimaryKeyConstraint("PK_" + tableName, "Id", "Id2") ); // Act - var columns = Provider.GetColumns(tableName); + var columns = Provider.ReadLegacyColumns(tableName); // Assert Assert.That(columns[0].IsNullable, Is.False); @@ -82,7 +82,7 @@ public void GetColumns_AddUniqueConstraintWithTwoColumns_NoUniqueOnColumnLevel() Provider.AddUniqueConstraint(constraintName, tableName, column1Name, column2Name); // Act - var columns = Provider.GetColumns(tableName); + var columns = Provider.ReadLegacyColumns(tableName); // Assert Assert.That(columns[0].IsNullable, Is.True); diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PRAGMAForeignKeys.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PRAGMAForeignKeys.cs deleted file mode 100644 index 189791ce..00000000 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PRAGMAForeignKeys.cs +++ /dev/null @@ -1,53 +0,0 @@ - - - - -// Does not work because we cannot reuse the connection at this point in time. - - - - - - -// using System.Data; -// using DotNetProjects.Migrator.Providers.Impl.SQLite; -// using Migrator.Framework; -// using Migrator.Tests.Providers.SQLite.Base; -// using NUnit.Framework; - -// namespace Migrator.Tests.Providers.SQLite; - -// [TestFixture] -// [Category("SQLite")] -// public class SQLiteTransformationProvider_PRAGMAForeignKeysTests : SQLiteTransformationProviderTestBase -// { -// [Test, Description("Tests the set ON indirectly. Integrity violation should throw.")] -// public void PragmaForeignKeys_IntegrityViolation_Throws() -// { -// const string parentTableName = "ParentTable"; -// const string childTableName = "ChildTable"; -// const string propertyIdName = "Id"; -// const string foreignKeyColumnName = "ParentId"; - -// Provider.AddTable(parentTableName, new Column(propertyIdName, DbType.Int32, ColumnProperty.PrimaryKey)); -// Provider.AddTable(childTableName, new Column(propertyIdName, DbType.Int32, ColumnProperty.PrimaryKey), new Column(foreignKeyColumnName, DbType.Int32)); - -// ((SQLiteTransformationProvider)Provider).BeginTransaction(); -// ((SQLiteTransformationProvider)Provider).SetPragmaForeignKeys(false); -// var pragmaForeignKeyState1 = ((SQLiteTransformationProvider)Provider).IsPragmaForeignKeysOn(); - -// Provider.ExecuteNonQuery($"INSERT INTO {parentTableName} ({propertyIdName}) VALUES (1)"); - -// // Integrity violation does not throw due to set OFF validation -// Provider.ExecuteNonQuery($"INSERT INTO {childTableName} ({propertyIdName}, {foreignKeyColumnName}) VALUES (1, 999)"); - -// Provider.ExecuteNonQuery($"DELETE FROM {childTableName}"); - -// ((SQLiteTransformationProvider)Provider).SetPragmaForeignKeys(true); -// var pragmaForeignKeyState2 = ((SQLiteTransformationProvider)Provider).IsPragmaForeignKeysOn(); - - -// Assert.That(pragmaForeignKeyState1, Is.False); -// Assert.That(pragmaForeignKeyState2, Is.True); -// } -// } diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs index 2007ef03..dad73cae 100644 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_PropertyColumnIdentityTests.cs @@ -25,7 +25,7 @@ public void AddPrimaryIdentity_Succeeds() var sql = ((SQLiteTransformationProvider)Provider).GetSqlCreateTableScript(testTableName); // NOT NULL implicitly set in SQLite - Assert.That(Provider.GetColumnByName(testTableName, "Color1").IsIdentity, Is.True); - Assert.That(Provider.GetColumnByName(testTableName, "Color1").IsNullable, Is.False); + Assert.That(Provider.ReadLegacyColumn(testTableName, "Color1").IsIdentity, Is.True); + Assert.That(Provider.ReadLegacyColumn(testTableName, "Color1").IsNullable, Is.False); } } diff --git a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs index 78ffbf08..6bdf085a 100644 --- a/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs +++ b/src/Migrator.Tests/Providers/SQLite/SQLiteTransformationProvider_RemoveAllConstraintsTests.cs @@ -34,7 +34,7 @@ public void RemoveColumn_HavingNoCompositeIndexAndNoCompositeUniqueConstraint_Su Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}, {propertyName2}) VALUES (1, 2)"); // Act - Provider.RemoveAllConstraints(testTableName); + Provider.RemoveLegacyConstraints(testTableName); Provider.ExecuteNonQuery($"INSERT INTO {testTableName} ({propertyName1}) VALUES (2)"); // Assert diff --git a/src/Migrator.Tests/SchemaConstraintTests.cs b/src/Migrator.Tests/SchemaConstraintTests.cs index e512a9a7..4cfd009c 100644 --- a/src/Migrator.Tests/SchemaConstraintTests.cs +++ b/src/Migrator.Tests/SchemaConstraintTests.cs @@ -137,7 +137,7 @@ public void RawDefaultsWorkInBothApisAndSurviveMetadataAndRebuild() builder.Apply(provider); foreach (var table in new[] { "RawImperative", "RawFluent" }) { - var defaultExpression = provider.GetColumns(table).Single(c => c.Name == "Token").DefaultValue; + var defaultExpression = provider.ReadLegacyColumns(table).Single(c => c.Name == "Token").DefaultValue; Assert.That(defaultExpression, Is.TypeOf()); provider.ChangeColumn(table, new Column("Id", DbType.Int64)); provider.ExecuteNonQuery("INSERT INTO " + table + " (Id) VALUES (1)"); @@ -179,7 +179,7 @@ public void MetadataDoesNotConfuseConcatenatedExpressionsWithStringLiterals() using var provider = ProviderFactory.Create(ProviderTypes.SQLite, "Data Source=:memory:", null); provider.AddTable("ConcatDefault", new Column("Id", DbType.Int32), new Column("Value", DbType.String, 30) { DefaultValue = RawSql.Insert("'A' || 'B'") }); - Assert.That(provider.GetColumns("ConcatDefault").Single(c => c.Name == "Value").DefaultValue, Is.TypeOf()); + Assert.That(provider.ReadLegacyColumns("ConcatDefault").Single(c => c.Name == "Value").DefaultValue, Is.TypeOf()); provider.ChangeColumn("ConcatDefault", new Column("Id", DbType.Int64)); provider.ExecuteNonQuery("INSERT INTO ConcatDefault (Id) VALUES (1)"); Assert.That(provider.ExecuteScalar("SELECT Value FROM ConcatDefault"), Is.EqualTo("AB")); diff --git a/src/Migrator.Tests/ToolingTests.cs b/src/Migrator.Tests/ToolingTests.cs index 7a81d65d..837b6546 100644 --- a/src/Migrator.Tests/ToolingTests.cs +++ b/src/Migrator.Tests/ToolingTests.cs @@ -42,6 +42,7 @@ public void CliReturnsMeaningfulArgumentExitCodes(string[] args, int exit) Assert.That(MigratorCommand.Run(args, output, error), Is.EqualTo(exit)); } [Migration(900002, Scope = "cli-spec")] + [Tags("cli", "shared")] public class CliMigration : DotNetProjects.Migrator.Framework.Fluent.AutoReversingMigration { public override void BuildUp(DotNetProjects.Migrator.Framework.Fluent.MigrationBuilder migration) @@ -133,4 +134,80 @@ [Test] public void CliCanListWithoutOpeningDatabase() Assert.That(exit, Is.Zero, error.ToString()); Assert.That(output.ToString(), Does.Contain("900001")); } + + [TestCase("--timeout", "-1")] + [TestCase("--timeout", "SECRET_VALUE")] + [TestCase("--lock-timeout", "2147483648")] + [TestCase("--transaction", "SECRET_VALUE")] + [TestCase("--tag-match", "99")] + [TestCase("--target", "-1")] + public void CliValidatesOptionsBeforeAssemblyLoadingOrDatabaseAccess(string option, string value) + { + using var output = new StringWriter(); + using var error = new StringWriter(); + var exit = MigratorCommand.Run(new[] { "migrate", "--assembly", "missing-migrations.dll", + "--provider", "SQLite", option, value }, output, error); + + Assert.That(exit, Is.EqualTo(2)); + Assert.That(error.ToString(), Does.Contain(option).And.Not.Contain(value)); + Assert.That(output.ToString(), Is.Empty); + } + + [TestCase(new[] { "--offline", "--offline" }, "--offline")] + [TestCase(new[] { "--timeout", "1", "--timeout", "2" }, "--timeout")] + [TestCase(new[] { "--timeout" }, "--timeout")] + [TestCase(new[] { "--timeout", "--lock" }, "--timeout")] + [TestCase(new[] { "--unknown" }, "--unknown")] + public void CliRejectsDuplicateUnknownAndIncompleteOptions(string[] options, string expectedOption) + { + using var output = new StringWriter(); + using var error = new StringWriter(); + var args = new[] { "list", "--assembly", typeof(ToolingTests).Assembly.Location, "--provider", "SQLite" }.Concat(options).ToArray(); + Assert.That(MigratorCommand.Run(args, output, error), Is.EqualTo(2)); + Assert.That(error.ToString(), Does.Contain(expectedOption)); + } + + [TestCase("cli,missing", "Any", true)] + [TestCase("cli,shared", "All", true)] + [TestCase("cli,missing", "All", false)] + [TestCase("missing", "Any", false)] + public void CliListFiltersByScopeAndTags(string tags, string match, bool selected) + { + using var output = new StringWriter(); + using var error = new StringWriter(); + var args = new[] { "list", "--assembly", typeof(ToolingTests).Assembly.Location, "--provider", "SQLite", + "--scope", "cli-spec", "--tags", tags, "--tag-match", match }; + Assert.That(MigratorCommand.Run(args, output, error), Is.Zero, error.ToString()); + Assert.That(output.ToString().Contains("900002"), Is.EqualTo(selected)); + Assert.That(output.ToString(), Does.Not.Contain("900001")); + } + + [Test] + public void CliOfflineSqlUsesTargetAndOutputWithoutAConnection() + { + var file = Path.GetTempFileName(); + try + { + using var output = new StringWriter(); + using var error = new StringWriter(); + var args = new[] { "sql", "--assembly", typeof(ToolingTests).Assembly.Location, "--provider", "SQLite", + "--scope", "cli-spec", "--tags", "cli", "--offline", "--target", "900002", "--output", file, + "--connection-env", "MISSING_" + Guid.NewGuid().ToString("N") }; + Assert.That(MigratorCommand.Run(args, output, error), Is.Zero, error.ToString()); + Assert.That(File.ReadAllText(file), Does.Contain("CREATE TABLE").And.Contain("CliExample")); + Assert.That(output.ToString(), Is.Empty); + } + finally { File.Delete(file); } + } + + [TestCase("plan", new[] { "--offline" })] + [TestCase("sql", new[] { "--offline", "--profiles", "example" })] + public void CliRejectsUnsupportedOfflineModes(string command, string[] options) + { + using var output = new StringWriter(); + using var error = new StringWriter(); + var args = new[] { command, "--assembly", typeof(ToolingTests).Assembly.Location, "--provider", "SQLite", "--scope", "cli-spec" } + .Concat(options).ToArray(); + Assert.That(MigratorCommand.Run(args, output, error), Is.EqualTo(3)); + } } diff --git a/src/Migrator.Tests/Tools/SchemaDumperTest.cs b/src/Migrator.Tests/Tools/SchemaDumperTest.cs deleted file mode 100644 index 9062f7b2..00000000 --- a/src/Migrator.Tests/Tools/SchemaDumperTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -//using System; -//using System.Configuration; -//using Migrator.Providers; -//using Migrator.Tools; -//using NUnit.Framework; - -//namespace Migrator.Tests.Tools; - -//[TestFixture] -//[Category("MySQL")] -//public class SchemaDumperTest -//{ -// [Test] -// public void Dump() -// { -// var constr = ConfigurationManager.AppSettings["MySqlConnectionString"]; - -// if (constr == null) -// { -// throw new ArgumentNullException("MySqlConnectionString", "No config file"); -// } - -// var dumper = new SchemaDumper(ProviderTypes.Mysql, constr, null); -// var output = dumper.GetDump(); - -// Assert.That(output, Is.Not.Null); -// } -//} - -//[TestFixture, Category("SqlServer2005")] -//public class SchemaDumperSqlServerTest -//{ -// [Test] -// public void Dump() -// { -// var constr = ConfigurationManager.AppSettings["SqlServerConnectionString"]; - -// if (constr == null) -// { -// throw new ArgumentNullException("SqlServerConnectionString", "No config file"); -// } - -// var dumper = new SchemaDumper(ProviderTypes.SqlServer, constr, ""); -// var output = dumper.GetDump(); - -// Assert.That(output, Is.Not.Null); -// } -//} diff --git a/src/Migrator.Tool/CliOptions.cs b/src/Migrator.Tool/CliOptions.cs new file mode 100644 index 00000000..33943154 --- /dev/null +++ b/src/Migrator.Tool/CliOptions.cs @@ -0,0 +1,104 @@ +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Providers; + +internal sealed class CliUsageException(string option) : Exception +{ + public string Option { get; } = option; +} + +internal sealed class CliOptions +{ + public string Command { get; private init; } + public string AssemblyPath { get; private init; } + public ProviderTypes Provider { get; private init; } + public string ConnectionEnvironment { get; private init; } + public string Schema { get; private init; } + public string Scope { get; private init; } + public long? Target { get; private init; } + public string[] Tags { get; private init; } + public TagMatchMode TagMatch { get; private init; } + public string[] Profiles { get; private init; } + public bool ProfilesSpecified { get; private init; } + public MigrationTransactionMode Transaction { get; private init; } + public int? CommandTimeout { get; private init; } + public int LockTimeout { get; private init; } + public bool UseLock { get; private init; } + public bool Offline { get; private init; } + public bool AllowLegacyPreview { get; private init; } + public string OutputPath { get; private init; } + + public static CliOptions Parse(string[] args) + { + var command = args[0]; + if (command is not ("list" or "status" or "validate" or "migrate" or "rollback" or "plan" or "sql")) + throw new CliUsageException("command"); + + var values = ReadArguments(args); + string Value(string key, string fallback = null) => values.GetValueOrDefault(key, fallback); + var provider = ParseEnum(Value("--provider", "none"), "--provider"); + if (provider == ProviderTypes.none) throw new CliUsageException("--provider"); + var assembly = Value("--assembly") ?? throw new CliUsageException("--assembly"); + long? target = null; + if (Value("--target") is { } targetValue) + { + if (!long.TryParse(targetValue, out var parsed) || parsed < 0) throw new CliUsageException("--target"); + target = parsed; + } + if (command == "rollback" && target == null) throw new CliUsageException("--target"); + + return new CliOptions + { + Command = command, + AssemblyPath = Path.GetFullPath(assembly), + Provider = provider, + ConnectionEnvironment = Value("--connection-env", "MIGRATOR_CONNECTION"), + Scope = Value("--scope", "default"), + Schema = Value("--schema"), + Target = target, + Tags = Split(Value("--tags", "")), + TagMatch = ParseEnum(Value("--tag-match", "Any"), "--tag-match"), + Profiles = Split(Value("--profiles", "")), + ProfilesSpecified = values.ContainsKey("--profiles"), + Transaction = ParseEnum(Value("--transaction", "PerMigration"), "--transaction"), + CommandTimeout = Value("--timeout") is { } timeout ? ParseSeconds(timeout, "--timeout") : null, + LockTimeout = ParseSeconds(Value("--lock-timeout", "30"), "--lock-timeout"), + UseLock = values.ContainsKey("--lock"), + Offline = values.ContainsKey("--offline"), + AllowLegacyPreview = values.ContainsKey("--allow-legacy-preview"), + OutputPath = Value("--output") + }; + } + + private static Dictionary ReadArguments(string[] args) + { + var values = new Dictionary(StringComparer.Ordinal); + for (var i = 1; i < args.Length; i++) + { + var key = args[i]; + if (values.ContainsKey(key)) throw new CliUsageException(key); + switch (key) + { + case "--lock" or "--offline" or "--allow-legacy-preview": + values.Add(key, "true"); + break; + case "--assembly" or "--provider" or "--connection-env" or "--scope" or "--schema" or "--target" + or "--tags" or "--tag-match" or "--profiles" or "--transaction" or "--timeout" or "--lock-timeout" or "--output": + if (i + 1 == args.Length || args[i + 1].StartsWith("--", StringComparison.Ordinal)) + throw new CliUsageException(key); + values.Add(key, args[++i]); + break; + default: + throw new CliUsageException(key); + } + } + return values; + } + + private static T ParseEnum(string value, string option) where T : struct, Enum => + Enum.TryParse(value, true, out var result) && Enum.IsDefined(result) ? result : throw new CliUsageException(option); + + private static int ParseSeconds(string value, string option) => + int.TryParse(value, out var seconds) && seconds >= 0 ? seconds : throw new CliUsageException(option); + + private static string[] Split(string value) => value.Split(',', StringSplitOptions.RemoveEmptyEntries); +} diff --git a/src/Migrator.Tool/MigratorCommand.cs b/src/Migrator.Tool/MigratorCommand.cs new file mode 100644 index 00000000..c2078a0e --- /dev/null +++ b/src/Migrator.Tool/MigratorCommand.cs @@ -0,0 +1,142 @@ +using System.Data.Common; +using System.Reflection; +using System.Runtime.Loader; +using DotNetProjects.Migrator; +using DotNetProjects.Migrator.Framework; +using DotNetProjects.Migrator.Framework.Loggers; +using DotNetProjects.Migrator.Providers; + +public static class MigratorCommand +{ + public static int Run(string[] args, TextWriter output, TextWriter error) + { + try + { + if (args.Length == 0 || args.Contains("--help")) WriteHelp(output); + else Execute(CliOptions.Parse(args), output); + return 0; + } + catch (CliUsageException ex) { error.WriteLine("Invalid arguments: " + ex.Option + ". Use --help."); return 2; } + catch (UnsupportedMigrationFeatureException) { error.WriteLine("The requested operation is unsupported by this provider or preview mode."); return 3; } + catch (MigrationLockTimeoutException) { error.WriteLine("Migration lock acquisition timed out."); return 4; } + catch (Exception ex) { error.WriteLine("Migration command failed (" + ex.GetType().Name + "). Exception details are omitted because they may contain credentials or SQL values."); return 1; } + } + + private static void WriteHelp(TextWriter output) + { + output.WriteLine("migrator --assembly PATH --provider NAME"); + output.WriteLine("--connection-env NAME (default MIGRATOR_CONNECTION), --scope NAME, --schema NAME, --target VERSION"); + output.WriteLine("--tags a,b --tag-match Any|All --profiles a,b --transaction PerMigration|None|WholeSession"); + output.WriteLine("--timeout SECONDS --lock --lock-timeout SECONDS --output PATH --offline --allow-legacy-preview"); + output.WriteLine("rollback requires --target. Offline SQL assumes empty history. Legacy preview executes trusted arbitrary C#."); + } + + private static void Execute(CliOptions options, TextWriter output) + { + var resolver = new AssemblyDependencyResolver(options.AssemblyPath); + Assembly Resolving(AssemblyLoadContext context, AssemblyName name) + { + var path = resolver.ResolveAssemblyToPath(name); + return path == null ? null : context.LoadFromAssemblyPath(path); + } + + AssemblyLoadContext.Default.Resolving += Resolving; + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(options.AssemblyPath); + var types = MigrationLoader.GetMigrationTypes(assembly).Where(t => InScope(t, options.Scope)).ToArray(); + var versioned = types.Where(t => t.GetCustomAttribute() != null && Selected(t, options)) + .OrderBy(MigrationLoader.GetMigrationVersion).ToArray(); + var target = options.Target ?? versioned.Select(MigrationLoader.GetMigrationVersion).DefaultIfEmpty(0).Max(); + + if (options.Command == "list") + { + foreach (var type in versioned) output.WriteLine(MigrationLoader.GetMigrationVersion(type) + " " + type.FullName); + } + else if (options.Offline) WriteOfflineSql(options, types, versioned, target, output); + else ExecuteConnected(options, types, target, output); + } + finally { AssemblyLoadContext.Default.Resolving -= Resolving; } + } + + private static bool InScope(Type type, string scope) => + (type.GetCustomAttribute()?.Scope ?? type.GetCustomAttribute()?.Scope + ?? type.GetCustomAttribute()?.Scope) is not string ownScope || ownScope == scope; + + private static bool Selected(Type type, CliOptions options) + { + var tags = type.GetCustomAttribute()?.Tags ?? Array.Empty(); + return options.Tags.Length == 0 || (options.TagMatch == TagMatchMode.All ? options.Tags.All(tags.Contains) : options.Tags.Any(tags.Contains)); + } + + private static void WriteOfflineSql(CliOptions options, Type[] types, Type[] versioned, long target, TextWriter output) + { + if (options.Command != "sql" || options.ProfilesSpecified || types.Any(t => t.GetCustomAttribute() != null)) + throw new UnsupportedMigrationFeatureException("CLI operation is unsupported."); + var plan = MigrationPlanner.Create(versioned.Select(MigrationLoader.GetMigrationVersion), Array.Empty(), target); + var migrations = plan.Select(step => ((IMigration)Activator.CreateInstance(versioned.Single(t => MigrationLoader.GetMigrationVersion(t) == step.Version)), step.IsUp)); + WriteSql(options, MigrationSqlPreview.Generate(options.Provider, migrations, options.AllowLegacyPreview), output); + } + + private static void ExecuteConnected(CliOptions options, Type[] types, long target, TextWriter output) + { + var connectionString = Environment.GetEnvironmentVariable(options.ConnectionEnvironment) ?? throw new CliUsageException("--connection-env"); + var providerName = RegisterDriver(options.Provider); + using var provider = ProviderFactory.Create(options.Provider, connectionString, options.Schema, options.Scope, providerName); + if (options.CommandTimeout is { } timeout) provider.CommandTimeout = timeout; + var runner = new Migrator(provider, false, new Logger(false), types); + runner.Options.Tags.UnionWith(options.Tags); + runner.Options.TagMatch = options.TagMatch; + runner.Options.Profiles.UnionWith(options.Profiles); + runner.Options.TransactionMode = options.Transaction; + if (options.UseLock) runner.Options.Lock = new DatabaseMigrationLock(); + runner.Options.LockTimeout = TimeSpan.FromSeconds(options.LockTimeout); + + switch (options.Command) + { + case "status": + foreach (var applied in ((IMigrationHistory)provider).ReadAppliedMigrations()) output.WriteLine(applied + " applied"); + break; + case "validate": + _ = runner.Plan(target); + output.WriteLine("Migration plan is valid."); + break; + case "plan": + foreach (var step in runner.Plan(target)) output.WriteLine(step.Version + (step.IsUp ? " up" : " down")); + break; + case "sql": + WriteSql(options, runner.PreviewSql(target, options.Provider, options.AllowLegacyPreview), output); + break; + case "rollback": + runner.RollbackTo(target); + output.WriteLine("Rollback completed."); + break; + case "migrate": + runner.MigrateTo(target); + output.WriteLine("Migration completed."); + break; + } + } + + private static string RegisterDriver(ProviderTypes provider) + { + (string name, DbProviderFactory factory) = provider switch + { + ProviderTypes.SQLite => ("Microsoft.Data.Sqlite", (DbProviderFactory)Microsoft.Data.Sqlite.SqliteFactory.Instance), + ProviderTypes.SqlServer or ProviderTypes.SqlServer2005 => ("Microsoft.Data.SqlClient", Microsoft.Data.SqlClient.SqlClientFactory.Instance), + ProviderTypes.PostgreSQL or ProviderTypes.PostgreSQL82 => ("Npgsql", Npgsql.NpgsqlFactory.Instance), + ProviderTypes.Mysql or ProviderTypes.MariaDB => ("MySql.Data.MySqlClient", MySql.Data.MySqlClient.MySqlClientFactory.Instance), + ProviderTypes.Oracle => ("Oracle.ManagedDataAccess.Client", Oracle.ManagedDataAccess.Client.OracleClientFactory.Instance), + ProviderTypes.Firebird => ("FirebirdSql.Data.FirebirdClient", FirebirdSql.Data.FirebirdClient.FirebirdClientFactory.Instance), + _ => throw new UnsupportedMigrationFeatureException("CLI operation is unsupported.") + }; + System.Data.Common.DbProviderFactories.RegisterFactory(name, factory); + return name; + } + + private static void WriteSql(CliOptions options, string sql, TextWriter output) + { + if (options.OutputPath is { } path) File.WriteAllText(path, sql); + else output.WriteLine(sql); + } +} diff --git a/src/Migrator.Tool/Program.cs b/src/Migrator.Tool/Program.cs index 93701fe1..22cd77e1 100644 --- a/src/Migrator.Tool/Program.cs +++ b/src/Migrator.Tool/Program.cs @@ -1,127 +1 @@ -using System.Reflection; -using System.Runtime.Loader; -using DotNetProjects.Migrator; -using DotNetProjects.Migrator.Framework; -using DotNetProjects.Migrator.Framework.Loggers; -using DotNetProjects.Migrator.Providers; - return MigratorCommand.Run(args, Console.Out, Console.Error); - -public static class MigratorCommand -{ - public static int Run(string[] args, TextWriter output, TextWriter error) - { - try { return Execute(args, output); } - catch (CliUsageException ex) { error.WriteLine("Invalid arguments: " + ex.Option + ". Use --help."); return 2; } - catch (UnsupportedMigrationFeatureException) { error.WriteLine("The requested operation is unsupported by this provider or preview mode."); return 3; } - catch (MigrationLockTimeoutException) { error.WriteLine("Migration lock acquisition timed out."); return 4; } - catch (Exception ex) { error.WriteLine("Migration command failed (" + ex.GetType().Name + "). Exception details are omitted because they may contain credentials or SQL values."); return 1; } - } - private sealed class CliUsageException(string option) : Exception { public string Option { get; } = option; } - private static int Execute(string[] args, TextWriter output) - { - if (args.Length == 0 || args.Contains("--help")) - { - output.WriteLine("migrator --assembly PATH --provider NAME"); - output.WriteLine("--connection-env NAME (default MIGRATOR_CONNECTION), --scope NAME, --schema NAME, --target VERSION"); - output.WriteLine("--tags a,b --tag-match Any|All --profiles a,b --transaction PerMigration|None|WholeSession"); - output.WriteLine("--timeout SECONDS --lock --lock-timeout SECONDS --output PATH --offline --allow-legacy-preview"); - output.WriteLine("rollback requires --target. Offline SQL assumes empty history. Legacy preview executes trusted arbitrary C#."); - return 0; - } - var command = args[0]; - if (!new[] { "list", "status", "validate", "migrate", "rollback", "plan", "sql" }.Contains(command)) throw new CliUsageException("command"); - var values = new Dictionary(StringComparer.Ordinal); - var flags = new HashSet { "--lock", "--offline", "--allow-legacy-preview" }; - var allowed = new HashSet { "--assembly", "--provider", "--connection-env", "--scope", "--schema", "--target", "--tags", "--tag-match", "--profiles", "--transaction", "--timeout", "--lock-timeout", "--output" }; - for (var i = 1; i < args.Length; i++) - { - var key = args[i]; - if (values.ContainsKey(key)) throw new CliUsageException(key); - if (flags.Contains(key)) values.Add(key, "true"); - else if (allowed.Contains(key) && i + 1 < args.Length && !args[i + 1].StartsWith("--")) values.Add(key, args[++i]); - else throw new CliUsageException(key); - } - string Value(string key, string fallback = null) => values.GetValueOrDefault(key, fallback); - T EnumValue(string key, string fallback) where T : struct, Enum => Enum.TryParse(Value(key, fallback), true, out var result) && Enum.IsDefined(result) ? result : throw new CliUsageException(key); - var providerType = EnumValue("--provider", "none"); - if (providerType == ProviderTypes.none) throw new CliUsageException("--provider"); - var assemblyPath = Path.GetFullPath(Value("--assembly") ?? throw new CliUsageException("--assembly")); - var resolver = new AssemblyDependencyResolver(assemblyPath); - Assembly Resolving(AssemblyLoadContext context, AssemblyName name) - { - var path = resolver.ResolveAssemblyToPath(name); - return path == null ? null : context.LoadFromAssemblyPath(path); - } - AssemblyLoadContext.Default.Resolving += Resolving; - try - { - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); - var scope = Value("--scope", "default"); - var types = MigrationLoader.GetMigrationTypes(assembly).Where(t => - (t.GetCustomAttribute()?.Scope ?? t.GetCustomAttribute()?.Scope ?? t.GetCustomAttribute()?.Scope) is not string ownScope || ownScope == scope).ToArray(); - var tags = Value("--tags", "").Split(',', StringSplitOptions.RemoveEmptyEntries); - var tagMatch = EnumValue("--tag-match", "Any"); - bool Selected(Type t) - { - var own = t.GetCustomAttribute()?.Tags ?? Array.Empty(); - return tags.Length == 0 || (tagMatch == TagMatchMode.All ? tags.All(own.Contains) : tags.Any(own.Contains)); - } - var versioned = types.Where(t => t.GetCustomAttribute() != null && Selected(t)).OrderBy(MigrationLoader.GetMigrationVersion).ToArray(); - var target = Value("--target") is { } targetString ? long.TryParse(targetString, out var parsed) && parsed >= 0 ? parsed : throw new CliUsageException("--target") : versioned.Select(MigrationLoader.GetMigrationVersion).DefaultIfEmpty(0).Max(); - if (command == "rollback" && !values.ContainsKey("--target")) throw new CliUsageException("--target"); - if (command == "list") - { - foreach (var type in versioned) output.WriteLine(MigrationLoader.GetMigrationVersion(type) + " " + type.FullName); - return 0; - } - if (values.ContainsKey("--offline")) - { - if (command != "sql" || values.ContainsKey("--profiles") || types.Any(t => t.GetCustomAttribute() != null)) throw new UnsupportedMigrationFeatureException("CLI operation is unsupported."); - var plan = MigrationPlanner.Create(versioned.Select(MigrationLoader.GetMigrationVersion), Array.Empty(), target); - var migrations = plan.Select(step => ((IMigration)Activator.CreateInstance(versioned.Single(t => MigrationLoader.GetMigrationVersion(t) == step.Version)), step.IsUp)); - Write(MigrationSqlPreview.Generate(providerType, migrations, values.ContainsKey("--allow-legacy-preview"))); - return 0; - } - var connectionString = Environment.GetEnvironmentVariable(Value("--connection-env", "MIGRATOR_CONNECTION")) ?? throw new CliUsageException("--connection-env"); - var providerName = providerType switch - { - ProviderTypes.SQLite => "Microsoft.Data.Sqlite", ProviderTypes.SqlServer or ProviderTypes.SqlServer2005 => "Microsoft.Data.SqlClient", - ProviderTypes.PostgreSQL or ProviderTypes.PostgreSQL82 => "Npgsql", ProviderTypes.Mysql or ProviderTypes.MariaDB => "MySql.Data.MySqlClient", - ProviderTypes.Oracle => "Oracle.ManagedDataAccess.Client", ProviderTypes.Firebird => "FirebirdSql.Data.FirebirdClient", - _ => throw new UnsupportedMigrationFeatureException("CLI operation is unsupported.") - }; - System.Data.Common.DbProviderFactories.RegisterFactory(providerName, providerType switch - { - ProviderTypes.SQLite => Microsoft.Data.Sqlite.SqliteFactory.Instance, - ProviderTypes.SqlServer or ProviderTypes.SqlServer2005 => Microsoft.Data.SqlClient.SqlClientFactory.Instance, - ProviderTypes.PostgreSQL or ProviderTypes.PostgreSQL82 => Npgsql.NpgsqlFactory.Instance, - ProviderTypes.Mysql or ProviderTypes.MariaDB => MySql.Data.MySqlClient.MySqlClientFactory.Instance, - ProviderTypes.Oracle => Oracle.ManagedDataAccess.Client.OracleClientFactory.Instance, - ProviderTypes.Firebird => FirebirdSql.Data.FirebirdClient.FirebirdClientFactory.Instance, - _ => throw new UnsupportedMigrationFeatureException("CLI operation is unsupported.") - }); - using var provider = ProviderFactory.Create(providerType, connectionString, Value("--schema"), scope, providerName); - if (values.ContainsKey("--timeout")) provider.CommandTimeout = Seconds("--timeout", "30"); - var runner = new Migrator(provider, false, new Logger(false), types); - runner.Options.Tags.UnionWith(tags); runner.Options.TagMatch = tagMatch; - runner.Options.Profiles.UnionWith(Value("--profiles", "").Split(',', StringSplitOptions.RemoveEmptyEntries)); - runner.Options.TransactionMode = EnumValue("--transaction", "PerMigration"); - if (values.ContainsKey("--lock")) runner.Options.Lock = new DatabaseMigrationLock(); - runner.Options.LockTimeout = TimeSpan.FromSeconds(Seconds("--lock-timeout", "30")); - switch (command) - { - case "status": foreach (var applied in ((IMigrationHistory)provider).ReadAppliedMigrations()) output.WriteLine(applied + " applied"); break; - case "validate": _ = runner.Plan(target); output.WriteLine("Migration plan is valid."); break; - case "plan": foreach (var step in runner.Plan(target)) output.WriteLine(step.Version + (step.IsUp ? " up" : " down")); break; - case "sql": Write(runner.PreviewSql(target, providerType, values.ContainsKey("--allow-legacy-preview"))); break; - case "rollback": runner.RollbackTo(target); output.WriteLine("Rollback completed."); break; - default: runner.MigrateTo(target); output.WriteLine("Migration completed."); break; - } - return 0; - int Seconds(string key, string fallback) => int.TryParse(Value(key, fallback), out var seconds) && seconds >= 0 ? seconds : throw new CliUsageException(key); - void Write(string sql) { if (Value("--output") is { } path) File.WriteAllText(path, sql); else output.WriteLine(sql); } - } - finally { AssemblyLoadContext.Default.Resolving -= Resolving; } - } -} diff --git a/src/Migrator/AssemblyInfo.cs b/src/Migrator/AssemblyInfo.cs index 93009494..7674c8dc 100644 --- a/src/Migrator/AssemblyInfo.cs +++ b/src/Migrator/AssemblyInfo.cs @@ -2,3 +2,4 @@ [assembly: AssemblyTitle("DotNetProjects.Migrator")] [assembly: AssemblyDescription("DotNetProjects.Migrator Core")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Migrator.Tests")] diff --git a/src/Migrator/Framework/StringUtils.cs b/src/Migrator/Framework/StringUtils.cs index 5e2be26f..f8e4d810 100644 --- a/src/Migrator/Framework/StringUtils.cs +++ b/src/Migrator/Framework/StringUtils.cs @@ -1,3 +1,4 @@ +using System; using System.Text; using System.Text.RegularExpressions; @@ -13,9 +14,11 @@ public class StringUtils /// public static string ToHumanName(string className) { + ArgumentNullException.ThrowIfNull(className); var name = Regex.Replace(className, "^[_0-9]*|[_0-9]*$", ""); - name = Regex.Replace(name, "([A-Z])", " $1").Substring(1); + name = Regex.Replace(name, "([A-Z])", " $1").TrimStart(); + if (name.Length == 0) return name; return name.Substring(0, 1).ToUpper() + name.Substring(1).ToLower(); } @@ -29,7 +32,7 @@ public static string ToHumanName(string className) /// public static string ReplaceOnce(string template, string placeholder, string replacement) { - var loc = template.IndexOf(placeholder); + var loc = template.IndexOf(placeholder, StringComparison.Ordinal); if (loc < 0) { return template; diff --git a/src/Migrator/MigrationLoader.cs b/src/Migrator/MigrationLoader.cs index 932164b3..d6d5f7d6 100644 --- a/src/Migrator/MigrationLoader.cs +++ b/src/Migrator/MigrationLoader.cs @@ -21,14 +21,7 @@ public MigrationLoader(ITransformationProvider provider, Assembly migrationAssem _provider = provider; AddMigrations(migrationAssembly); - if (trace) - { - provider.Logger.Trace("Loaded migrations:"); - foreach (var t in _migrationsTypes) - { - provider.Logger.Trace("{0} {1}", (t.GetCustomAttribute()?.Version.ToString() ?? "aux").PadLeft(5), StringUtils.ToHumanName(t.Name)); - } - } + if (trace) TraceMigrations(); } public MigrationLoader(ITransformationProvider provider, bool trace, params Type[] migrationTypes) @@ -36,14 +29,14 @@ public MigrationLoader(ITransformationProvider provider, bool trace, params Type _provider = provider; _migrationsTypes.AddRange(migrationTypes); - if (trace) - { - provider.Logger.Trace("Loaded migrations:"); - foreach (var t in _migrationsTypes) - { - provider.Logger.Trace("{0} {1}", (t.GetCustomAttribute()?.Version.ToString() ?? "aux").PadLeft(5), StringUtils.ToHumanName(t.Name)); - } - } + if (trace) TraceMigrations(); + } + + private void TraceMigrations() + { + _provider.Logger.Trace("Loaded migrations:"); + foreach (var type in _migrationsTypes) + _provider.Logger.Trace("{0} {1}", (type.GetCustomAttribute()?.Version.ToString() ?? "aux").PadLeft(5), StringUtils.ToHumanName(type.Name)); } /// @@ -93,17 +86,15 @@ public virtual void AddMigrations(Assembly migrationAssembly) /// CheckForDuplicatedVersion public virtual void CheckForDuplicatedVersion() { - var versions = new List(); + var versions = new HashSet(); foreach (var t in SelectedTypes) { var version = GetMigrationVersion(t); - if (versions.Contains(version)) + if (!versions.Add(version)) { throw new DuplicatedVersionException(version); } - - versions.Add(version); } } diff --git a/src/Migrator/ProviderFactory.cs b/src/Migrator/ProviderFactory.cs index df7f7931..1b90d488 100644 --- a/src/Migrator/ProviderFactory.cs +++ b/src/Migrator/ProviderFactory.cs @@ -1,4 +1,3 @@ -using System; using System.Data; using DotNetProjects.Migrator.Framework; using DotNetProjects.Migrator.Providers; @@ -20,11 +19,8 @@ namespace DotNetProjects.Migrator; /// public class ProviderFactory { - static ProviderFactory() - { } - /// - /// + /// /// /// /// @@ -53,35 +49,35 @@ public static Dialect DialectForProvider(ProviderTypes providerType) case ProviderTypes.Hana: return new DotNetProjects.Migrator.Providers.Impl.Hana.HanaDialect(); case ProviderTypes.SQLite: - return (Dialect)Activator.CreateInstance(typeof(SQLiteDialect)); + return new SQLiteDialect(); case ProviderTypes.MonoSQLite: - return (Dialect)Activator.CreateInstance(typeof(SQLiteMonoDialect)); + return new SQLiteMonoDialect(); case ProviderTypes.Mysql: - return (Dialect)Activator.CreateInstance(typeof(MysqlDialect)); + return new MysqlDialect(); case ProviderTypes.MariaDB: - return (Dialect)Activator.CreateInstance(typeof(MariaDBDialect)); + return new MariaDBDialect(); case ProviderTypes.Oracle: - return (Dialect)Activator.CreateInstance(typeof(OracleDialect)); + return new OracleDialect(); case ProviderTypes.PostgreSQL: - return (Dialect)Activator.CreateInstance(typeof(PostgreSQLDialect)); + return new PostgreSQLDialect(); case ProviderTypes.PostgreSQL82: - return (Dialect)Activator.CreateInstance(typeof(PostgreSQL82Dialect)); + return new PostgreSQL82Dialect(); case ProviderTypes.SqlServer: - return (Dialect)Activator.CreateInstance(typeof(SqlServerDialect)); + return new SqlServerDialect(); case ProviderTypes.SqlServer2005: - return (Dialect)Activator.CreateInstance(typeof(SqlServer2005Dialect)); + return new SqlServer2005Dialect(); case ProviderTypes.MsOracle: - return (Dialect)Activator.CreateInstance(typeof(MsOracleDialect)); + return new MsOracleDialect(); case ProviderTypes.IBM_DB2: - return (Dialect)Activator.CreateInstance(typeof(DB2Dialect)); + return new DB2Dialect(); case ProviderTypes.IBM_Informix: - return (Dialect)Activator.CreateInstance(typeof(InformixDialect)); + return new InformixDialect(); case ProviderTypes.Firebird: - return (Dialect)Activator.CreateInstance(typeof(FirebirdDialect)); + return new FirebirdDialect(); case ProviderTypes.Ingres: - return (Dialect)Activator.CreateInstance(typeof(IngresDialect)); + return new IngresDialect(); case ProviderTypes.Sybase: - return (Dialect)Activator.CreateInstance(typeof(SybaseDialect)); + return new SybaseDialect(); } return null; diff --git a/src/Migrator/Providers/CatalogLiteralConversions.cs b/src/Migrator/Providers/CatalogLiteralConversions.cs new file mode 100644 index 00000000..0db14455 --- /dev/null +++ b/src/Migrator/Providers/CatalogLiteralConversions.cs @@ -0,0 +1,30 @@ +using System; +using System.Data; +using System.Globalization; + +namespace DotNetProjects.Migrator.Providers; + +// Compatibility metadata exposes signed/unsigned integers as Int64/UInt64. +// Oracle retains Single, while SQL Server and PostgreSQL expose it as Double. +internal static class CatalogLiteralConversions +{ + internal static bool IsText(DbType type) => + type is DbType.String or DbType.AnsiString or DbType.StringFixedLength or DbType.AnsiStringFixedLength; + + internal static bool TryParseNumber(string value, DbType type, bool preserveSingle, out object result) + { + result = type switch + { + DbType.Int16 or DbType.Int32 or DbType.Int64 => (object)long.Parse(value, CultureInfo.InvariantCulture), + DbType.UInt16 or DbType.UInt32 or DbType.UInt64 => ulong.Parse(value, CultureInfo.InvariantCulture), + DbType.Single when preserveSingle => float.Parse(value, CultureInfo.InvariantCulture), + DbType.Single or DbType.Double => double.Parse(value, CultureInfo.InvariantCulture), + DbType.Decimal => decimal.Parse(value, CultureInfo.InvariantCulture), + DbType.Byte => byte.Parse(value, CultureInfo.InvariantCulture), + _ => null + }; + return result != null; + } + + internal static byte[] ParseHex(string value) => Convert.FromHexString(value); +} diff --git a/src/Migrator/Providers/DbProviderFactoriesHelper.cs b/src/Migrator/Providers/DbProviderFactoriesHelper.cs index 4fc110b1..6ef7f179 100644 --- a/src/Migrator/Providers/DbProviderFactoriesHelper.cs +++ b/src/Migrator/Providers/DbProviderFactoriesHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Concurrent; using System.Data.Common; using System.Linq; using System.Reflection; @@ -10,29 +11,12 @@ public static class DbProviderFactoriesHelper { public static DbProviderFactory GetFactory(string providerName, string assemblyName, string factoryProviderType) { - try - { - var factory = DbProviderFactories.GetFactory(providerName); - if (factory != null) - { - return factory; - } - } - catch (Exception) - { } - + if (DbProviderFactories.TryGetFactory(providerName, out var factory) && factory != null) + return factory; #if !NETSTANDARD - try - { - var factory = System.Data.Common.DbProviderFactories.GetFactory(providerName); - if (factory != null) - { - return factory; - } - } - catch (Exception) - { } + if (System.Data.Common.DbProviderFactories.TryGetFactory(providerName, out factory) && factory != null) + return factory; #endif #if NETSTANDARD @@ -53,13 +37,13 @@ public static DbProviderFactory GetFactory(string providerName, string assemblyN public abstract class DbProviderFactories { - internal static readonly Dictionary> _configs = new Dictionary>(); + private static readonly ConcurrentDictionary> Factories = new(StringComparer.Ordinal); public static DbProviderFactory GetFactory(string providerInvariantName) { - if (_configs.ContainsKey(providerInvariantName)) + if (TryGetFactory(providerInvariantName, out var factory)) { - return _configs[providerInvariantName](); + return factory; } throw new Exception("ConfigProviderNotFound"); @@ -67,11 +51,24 @@ public static DbProviderFactory GetFactory(string providerInvariantName) public static void RegisterFactory(string providerInvariantName, Func factory) { - _configs[providerInvariantName] = factory; + ArgumentNullException.ThrowIfNull(factory); + Factories[providerInvariantName] = factory; + } + + internal static bool TryGetFactory(string providerInvariantName, out DbProviderFactory factory) + { + if (Factories.TryGetValue(providerInvariantName, out var createFactory)) + { + factory = createFactory(); + return true; + } + + factory = null; + return false; } public static IEnumerable GetFactoryProviderNames() { - return _configs.Keys.ToArray(); + return Factories.Keys.ToArray(); } } diff --git a/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs b/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs index 25d6da10..09cd7eed 100644 --- a/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/Mysql/MySqlTransformationProvider.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Data; using System.Globalization; -using System.Globalization; using System.Linq; using Index = DotNetProjects.Migrator.Framework.Index; diff --git a/src/Migrator/Providers/Impl/Oracle/OracleColumnDefault.cs b/src/Migrator/Providers/Impl/Oracle/OracleColumnDefault.cs new file mode 100644 index 00000000..57b19305 --- /dev/null +++ b/src/Migrator/Providers/Impl/Oracle/OracleColumnDefault.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.Oracle; + +internal static class OracleColumnDefault +{ + internal static void Apply(Column column, string dataDefaultString) + { + var timestampRegex = new Regex(@"(?<=^TIMESTAMP\s+')[^']+(?=')", RegexOptions.IgnoreCase); + var hexToRawRegex = new Regex(@"(?<=^HEXTORAW\s*\(')[^']+(?=')", RegexOptions.IgnoreCase); + var timestampBaseFormat = "yyyy-MM-dd HH:mm:ss"; + + // dataDefaultString contains ISEQ$$ if the column is an identity column + if ( + !string.IsNullOrWhiteSpace(dataDefaultString) && + !dataDefaultString.Trim().Equals("null", StringComparison.OrdinalIgnoreCase) && + !dataDefaultString.Contains("ISEQ$$") && + !dataDefaultString.Contains(".nextval")) + { + // This is only necessary because older versions of this migrator added single quotes for numerics. + var singleQuoteStrippedString = dataDefaultString.Replace("'", ""); + + var parsedDefault = CatalogDefaultValue.Parse(dataDefaultString, column.Type); + if (CatalogLiteralConversions.IsText(column.Type) + || (parsedDefault is RawSql && !Regex.IsMatch(dataDefaultString, + @"(?i)^\s*(TO_TIMESTAMP\s*\(|TIMESTAMP\s*'|HEXTORAW\s*\()"))) + column.DefaultValue = parsedDefault; + else if (CatalogLiteralConversions.TryParseNumber(singleQuoteStrippedString, column.Type, preserveSingle: true, out var number)) + column.DefaultValue = number; + else if (column.Type == DbType.Boolean) + { + column.DefaultValue = dataDefaultString == "1" || dataDefaultString.ToUpper() == "TRUE"; + } + else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + { + if (dataDefaultString.StartsWith("TO_TIMESTAMP(")) + { + var expectedOracleToTimestampPattern = "YYYY-MM-DD HH24:MI:SS"; + + if (!dataDefaultString.Contains(expectedOracleToTimestampPattern)) + { + throw new NotSupportedException($"Not supported 'TO_TIMESTAMP' pattern. Expected pattern: {expectedOracleToTimestampPattern}"); + } + + var toTimestampRegex = new Regex(@"(?<=^TO_TIMESTAMP\(')[^']+(?=')", RegexOptions.IgnoreCase); + var toTimestampMatch = toTimestampRegex.Match(dataDefaultString); + var toTimestampDateTimeString = toTimestampMatch.Value; + + List formats = []; + + // add formats with .F, .FF, .FFF etc. + formats = Enumerable.Range(0, 20).Select((x, y) => $"{timestampBaseFormat}.{new string('F', y + 1)}").ToList(); + formats.Add(timestampBaseFormat); + + column.DefaultValue = DateTime.ParseExact(toTimestampDateTimeString, [.. formats], CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } + else if (timestampRegex.Match(dataDefaultString) is Match timestampMatch && timestampMatch.Success) + { + var millisecondsPattern = column.Size == 0 ? string.Empty : $".{new string('F', column.Size)}"; + column.DefaultValue = DateTime.ParseExact(timestampMatch.Value, $"yyyy-MM-dd HH:mm:ss{millisecondsPattern}", CultureInfo.InvariantCulture); + } + else + { + // Could be system time in many variants + column.DefaultValue = dataDefaultString; + } + } + else if (column.Type == DbType.Guid) + { + if (hexToRawRegex.Match(dataDefaultString) is Match hexToRawMatch && hexToRawMatch.Success) + { + var bytes = CatalogLiteralConversions.ParseHex(hexToRawMatch.Value); + + // Oracle uses Big-Endian + Array.Reverse(bytes, 0, 4); + Array.Reverse(bytes, 4, 2); + Array.Reverse(bytes, 6, 2); + + column.DefaultValue = new Guid(bytes); + } + else if (dataDefaultString.StartsWith("'")) + { + var guidString = dataDefaultString.Substring(1, dataDefaultString.Length - 2); + + column.DefaultValue = Guid.Parse(guidString); + } + else + { + column.DefaultValue = dataDefaultString; + } + } + else if (column.Type == DbType.Binary) + { + if (hexToRawRegex.Match(dataDefaultString) is Match hexToRawMatch && hexToRawMatch.Success) + { + column.DefaultValue = CatalogLiteralConversions.ParseHex(hexToRawMatch.Value); + } + else + { + throw new NotImplementedException($"Cannot parse default value in column '{column.Name}'"); + } + } + else + { + column.DefaultValue = dataDefaultString; + } + } + } +} diff --git a/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs b/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs index 3e87b596..e2277924 100644 --- a/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/Oracle/OracleTransformationProvider.cs @@ -302,10 +302,6 @@ public override string[] GetTables() public override Column[] GetColumns(string table) { - var timestampRegex = new Regex(@"(?<=^TIMESTAMP\s+')[^']+(?=')", RegexOptions.IgnoreCase); - var hexToRawRegex = new Regex(@"(?<=^HEXTORAW\s*\(')[^']+(?=')", RegexOptions.IgnoreCase); - var timestampBaseFormat = "yyyy-MM-dd HH:mm:ss"; - var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("SELECT"); stringBuilder.AppendLine(" COLUMN_NAME,"); @@ -496,132 +492,7 @@ public override Column[] GetColumns(string table) throw new NotImplementedException($"The data type '{dataTypeString}' is not implemented yet. Please file an issue."); } - // dataDefaultString contains ISEQ$$ if the column is an identity column - if ( - !string.IsNullOrWhiteSpace(dataDefaultString) && - !dataDefaultString.Trim().Equals("null", StringComparison.OrdinalIgnoreCase) && - !dataDefaultString.Contains("ISEQ$$") && - !dataDefaultString.Contains(".nextval")) - { - // This is only necessary because older versions of this migrator added single quotes for numerics. - var singleQuoteStrippedString = dataDefaultString.Replace("'", ""); - - var parsedDefault = CatalogDefaultValue.Parse(dataDefaultString, column.Type); - if (column.Type is DbType.String or DbType.AnsiString or DbType.StringFixedLength or DbType.AnsiStringFixedLength - || (parsedDefault is RawSql && !Regex.IsMatch(dataDefaultString, - @"(?i)^\s*(TO_TIMESTAMP\s*\(|TIMESTAMP\s*'|HEXTORAW\s*\()"))) - column.DefaultValue = parsedDefault; - else if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) - { - column.DefaultValue = long.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Double) - { - column.DefaultValue = double.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Single) - { - column.DefaultValue = float.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Decimal) - { - column.DefaultValue = decimal.Parse(singleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Boolean) - { - column.DefaultValue = dataDefaultString == "1" || dataDefaultString.ToUpper() == "TRUE"; - } - else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) - { - if (dataDefaultString.StartsWith("TO_TIMESTAMP(")) - { - var expectedOracleToTimestampPattern = "YYYY-MM-DD HH24:MI:SS"; - - if (!dataDefaultString.Contains(expectedOracleToTimestampPattern)) - { - throw new NotSupportedException($"Not supported 'TO_TIMESTAMP' pattern. Expected pattern: {expectedOracleToTimestampPattern}"); - } - - var toTimestampRegex = new Regex(@"(?<=^TO_TIMESTAMP\(')[^']+(?=')", RegexOptions.IgnoreCase); - var toTimestampMatch = toTimestampRegex.Match(dataDefaultString); - var toTimestampDateTimeString = toTimestampMatch.Value; - - List formats = []; - - // add formats with .F, .FF, .FFF etc. - formats = Enumerable.Range(0, 20).Select((x, y) => $"{timestampBaseFormat}.{new string('F', y + 1)}").ToList(); - formats.Add(timestampBaseFormat); - - column.DefaultValue = DateTime.ParseExact(toTimestampDateTimeString, [.. formats], CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); - } - else if (timestampRegex.Match(dataDefaultString) is Match timestampMatch && timestampMatch.Success) - { - var millisecondsPattern = column.Size == 0 ? string.Empty : $".{new string('F', column.Size)}"; - column.DefaultValue = DateTime.ParseExact(timestampMatch.Value, $"yyyy-MM-dd HH:mm:ss{millisecondsPattern}", CultureInfo.InvariantCulture); - } - else - { - // Could be system time in many variants - column.DefaultValue = dataDefaultString; - } - } - else if (column.Type == DbType.Guid) - { - if (hexToRawRegex.Match(dataDefaultString) is Match hexToRawMatch && hexToRawMatch.Success) - { - var bytes = Enumerable.Range(0, hexToRawMatch.Value.Length / 2) - .Select(x => Convert.ToByte(hexToRawMatch.Value.Substring(x * 2, 2), 16)) - .ToArray(); - - // Oracle uses Big-Endian - Array.Reverse(bytes, 0, 4); - Array.Reverse(bytes, 4, 2); - Array.Reverse(bytes, 6, 2); - - column.DefaultValue = new Guid(bytes); - } - else if (dataDefaultString.StartsWith("'")) - { - var guidString = dataDefaultString.Substring(1, dataDefaultString.Length - 2); - - column.DefaultValue = Guid.Parse(guidString); - } - else - { - column.DefaultValue = dataDefaultString; - } - } - else if (column.Type == DbType.String) - { - var contentRegex = new Regex(@"(?<=^').*(?='$)"); - - if (contentRegex.Match(dataDefaultString) is Match contentMatch && contentMatch.Success) - { - column.DefaultValue = contentMatch.Value; - } - else - { - throw new Exception($"Cannot parse string column '{column.Name}'"); - } - } - else if (column.Type == DbType.Binary) - { - if (hexToRawRegex.Match(dataDefaultString) is Match hexToRawMatch && hexToRawMatch.Success) - { - column.DefaultValue = Enumerable.Range(0, hexToRawMatch.Value.Length / 2) - .Select(x => Convert.ToByte(hexToRawMatch.Value.Substring(x * 2, 2), 16)) - .ToArray(); - } - else - { - throw new NotImplementedException($"Cannot parse default value in column '{column.Name}'"); - } - } - else - { - column.DefaultValue = dataDefaultString; - } - } + OracleColumnDefault.Apply(column, dataDefaultString); columns.Add(column); } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 68b54dde..01a91f3a 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -33,7 +33,6 @@ namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; /// public class PostgreSQLTransformationProvider : TransformationProvider, IPostgreSQLTransformationProvider { - private Regex stripSingleQuoteRegEx = new("(?<=')[^']*(?=')"); private IPostgreSQLSystemDataLoader _postgreSQLSystemDataLoader; public PostgreSQLTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) @@ -493,7 +492,6 @@ public override Column[] GetColumns(string table) { var isNullable = columnInfo.IsNullable == "YES"; var isIdentity = columnInfo.IsIdentity == "YES"; - var isPrimaryKey = tableConstraints.Any(x => x.ColumnName.Equals(columnInfo.ColumnName, StringComparison.OrdinalIgnoreCase) && x.ConstraintType == "PRIMARY KEY"); MigratorDbType dbType = 0; int? precision = null; @@ -607,284 +605,12 @@ public override Column[] GetColumns(string table) column.IsNullable = isNullable; - if (isPrimaryKey) - { - - } - if (isIdentity) { column.IsIdentity = true; } - if (columnInfo.ColumnDefault != null && !isIdentity) - { - // Catalog casts on literal values retain the existing CLR conversion. - // All other expressions must survive inspection without evaluation or quoting. - var parsedDefault = CatalogDefaultValue.Parse(columnInfo.ColumnDefault, column.Type); - var isCastLiteral = Regex.IsMatch(columnInfo.ColumnDefault, - @"\A'(?:[^']|'')*'(?:::[A-Za-z0-9_ .\[\](),]+)?\z"); - if (column.Type is DbType.String or DbType.AnsiString or DbType.StringFixedLength or DbType.AnsiStringFixedLength) - { - var literal = Regex.Match(columnInfo.ColumnDefault, @"\A('(?:[^']|'')*')(?:::[A-Za-z0-9_ .\[\](),]+)?\z"); - column.DefaultValue = literal.Success - ? CatalogDefaultValue.Parse(literal.Groups[1].Value, column.Type) : parsedDefault; - } - else if (parsedDefault is RawSql && !isCastLiteral) - column.DefaultValue = parsedDefault; - else if (column.MigratorDbType == MigratorDbType.Int16 || column.MigratorDbType == MigratorDbType.Int32 || column.MigratorDbType == MigratorDbType.Int64) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - if (match.Success) - { - columnInfo.ColumnDefault = match.Value; - } - if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) - { - column.DefaultValue = long.Parse(columnInfo.ColumnDefault.ToString()); - } - } - else if (column.MigratorDbType == MigratorDbType.UInt16 || column.MigratorDbType == MigratorDbType.UInt32 || column.MigratorDbType == MigratorDbType.UInt64) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - if (match.Success) - { - columnInfo.ColumnDefault = match.Value; - } - if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) - { - column.DefaultValue = ulong.Parse(columnInfo.ColumnDefault.ToString()); - } - } - else if (column.MigratorDbType == MigratorDbType.Double || column.MigratorDbType == MigratorDbType.Single) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - if (match.Success) - { - columnInfo.ColumnDefault = match.Value; - } - if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) - { - column.DefaultValue = double.Parse(columnInfo.ColumnDefault.ToString(), CultureInfo.InvariantCulture); - } - } - else if (column.MigratorDbType == MigratorDbType.Time) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - if (!match.Success || !TimeOnly.TryParse(match.Value, CultureInfo.InvariantCulture, out var time)) - throw new NotSupportedException("Cannot parse PostgreSQL time default: " + columnInfo.ColumnDefault); - column.DefaultValue = time; - } - else if (column.MigratorDbType == MigratorDbType.Interval) - { - if (columnInfo.ColumnDefault.StartsWith("'")) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - - if (!match.Success) - { - throw new Exception("Postgre default value for interval: Single quotes around the interval string are expected."); - } - - var interval = Regex.Match(match.Value, @"^([+-]?)(\d+):(\d{2}):(\d{2}(?:\.\d{1,7})?)$"); - if (!interval.Success) throw new NotSupportedException("Cannot parse interval default: " + columnInfo.ColumnDefault); - var ticks = decimal.Parse(interval.Groups[2].Value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerHour - + decimal.Parse(interval.Groups[3].Value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerMinute - + decimal.Parse(interval.Groups[4].Value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond; - if (interval.Groups[1].Value == "-") ticks = -ticks; - column.DefaultValue = TimeSpan.FromTicks(checked((long)ticks)); - } - else - { - // We assume that the value was added using this migrator so we do not interpret things like '2 days 01:02:03' if you - // added such format you will run into this exception. - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}' unexpected pattern."); - } - } - else if (column.MigratorDbType == MigratorDbType.Boolean) - { - var truthy = new[] { "TRUE", "YES", "'true'", "on", "'on'", "t", "'t'" }; - var falsy = new[] { "FALSE", "NO", "'false'", "off", "'off'", "f", "'f'" }; - - if (truthy.Any(x => x.Equals(columnInfo.ColumnDefault.Trim(), StringComparison.OrdinalIgnoreCase))) - { - column.DefaultValue = true; - } - else if (falsy.Any(x => x.Equals(columnInfo.ColumnDefault.Trim(), StringComparison.OrdinalIgnoreCase))) - { - column.DefaultValue = false; - } - else - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - } - else if (column.MigratorDbType == MigratorDbType.DateTime || column.MigratorDbType == MigratorDbType.DateTime2) - { - if (columnInfo.ColumnDefault.StartsWith("'")) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - - if (!match.Success) - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - - var timeString = match.Value; - - // We convert to UTC since we restrict date time default values to UTC on default value definition. - var dateTimeExtracted = DateTime.ParseExact(timeString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); - - column.DefaultValue = dateTimeExtracted; - } - else - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - } - else if (column.MigratorDbType == MigratorDbType.Guid) - { - if (columnInfo.ColumnDefault.StartsWith("'")) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - - if (!match.Success) - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - - column.DefaultValue = Guid.Parse(match.Value); - } - else - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - } - else if (column.MigratorDbType == MigratorDbType.Decimal) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - if (match.Success) - { - columnInfo.ColumnDefault = match.Value; - } - if (!columnInfo.ColumnDefault.Contains(table, StringComparison.OrdinalIgnoreCase)) - { - column.DefaultValue = decimal.Parse(columnInfo.ColumnDefault, CultureInfo.InvariantCulture); - } - } - else if (column.MigratorDbType == MigratorDbType.String) - { - if (columnInfo.ColumnDefault.StartsWith("'")) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - - if (!match.Success) - { - throw new Exception("Postgre default value for date time: Single quotes around the date time string are expected."); - } - - column.DefaultValue = match.Value; - } - else - { - throw new NotImplementedException(); - } - } - else if (column.MigratorDbType == MigratorDbType.Binary) - { - if (columnInfo.ColumnDefault.StartsWith("'")) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - - if (!match.Success) - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - - var singleQuoteString = match.Value; - - if (!singleQuoteString.StartsWith("\\x")) - { - throw new Exception(@"Postgre \x notation expected."); - } - - var hexString = singleQuoteString.Substring(2); - - // Not available in old .NET version: Convert.FromHexString(hexString); - - column.DefaultValue = Enumerable.Range(0, hexString.Length / 2) - .Select(x => Convert.ToByte(hexString.Substring(x * 2, 2), 16)) - .ToArray(); - } - else - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - } - else if (column.MigratorDbType == MigratorDbType.DateTimeOffset) - { - if (columnInfo.ColumnDefault.StartsWith("'")) - { - var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault); - - if (!match.Success) - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - - var singleQuoteString = match.Value; - - // 1) Normalize "Z" at the end → "+00:00" - singleQuoteString = Regex.Replace(singleQuoteString, @"Z$", "+00:00"); - - // 2) Normalize offset at the end of the string - // Cases handled: - // +HH → +HH:00 - // +HHMM → +HH:MM - // +HH:MM → stays unchanged - // -HH / -HHMM → same logic - singleQuoteString = Regex.Replace( - singleQuoteString, - @"([+-])(\d{2})(?::?(\d{2}))?$", - m => - { - var sign = m.Groups[1].Value; // "+" or "-" - var hh = m.Groups[2].Value; // hours - var hasMm = m.Groups[3].Success; // minutes present? - var mm = hasMm ? m.Groups[3].Value : "00"; - return $"{sign}{hh}:{mm}"; - } - ); - - // 3) Parse using multiple possible formats - // Supports both space and "T" separator, with/without milliseconds - var formats = new[] - { - "yyyy-MM-dd HH:mm:ss.fffzzz", // space separator, with ms - "yyyy-MM-dd HH:mm:sszzz", // space separator, no ms - "yyyy-MM-ddTHH:mm:ss.fffzzz", // ISO8601, with ms - "yyyy-MM-ddTHH:mm:sszzz" // ISO8601, no ms - }; - - var dateTimeOffset = DateTimeOffset.ParseExact( - singleQuoteString, - formats, - CultureInfo.InvariantCulture, - DateTimeStyles.None - ); - - column.DefaultValue = dateTimeOffset; - } - else - { - throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'"); - } - } - else - { - throw new NotImplementedException($"{nameof(DbType)} {column.MigratorDbType} not implemented."); - } - } + if (!isIdentity) PostgreSqlColumnDefault.Apply(column, columnInfo.ColumnDefault); columns.Add(column); } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSqlColumnDefault.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSqlColumnDefault.cs new file mode 100644 index 00000000..4e31f812 --- /dev/null +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSqlColumnDefault.cs @@ -0,0 +1,235 @@ +using System; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; + +internal static class PostgreSqlColumnDefault +{ + private static readonly Regex stripSingleQuoteRegEx = new("(?<=')[^']*(?=')"); + + internal static void Apply(Column column, string source) + { + if (source != null) + { + // Catalog casts on literal values retain the existing CLR conversion. + // All other expressions must survive inspection without evaluation or quoting. + var parsedDefault = CatalogDefaultValue.Parse(source, column.Type); + if (parsedDefault == null) + { + column.DefaultValue = null; + return; + } + var isCastLiteral = Regex.IsMatch(source, + @"\A'(?:[^']|'')*'(?:::[A-Za-z0-9_ .\[\](),]+)?\z"); + if (CatalogLiteralConversions.IsText(column.Type)) + { + var literal = Regex.Match(source, @"\A('(?:[^']|'')*')(?:::[A-Za-z0-9_ .\[\](),]+)?\z"); + column.DefaultValue = literal.Success + ? CatalogDefaultValue.Parse(literal.Groups[1].Value, column.Type) : parsedDefault; + } + else if (parsedDefault is RawSql && !isCastLiteral) + column.DefaultValue = parsedDefault; + else if (IsNumeric(column.Type)) + { + var match = stripSingleQuoteRegEx.Match(source); + var literal = match.Success ? match.Value : source; + if (CatalogLiteralConversions.TryParseNumber(literal, column.Type, preserveSingle: false, out var number)) + column.DefaultValue = number; + } + else if (column.MigratorDbType == MigratorDbType.Time) + { + var match = stripSingleQuoteRegEx.Match(source); + if (!match.Success || !TimeOnly.TryParse(match.Value, CultureInfo.InvariantCulture, out var time)) + throw new NotSupportedException("Cannot parse PostgreSQL time default: " + source); + column.DefaultValue = time; + } + else if (column.MigratorDbType == MigratorDbType.Interval) + ApplyInterval(column, source); + else if (column.MigratorDbType == MigratorDbType.Boolean) + { + var truthy = new[] { "TRUE", "YES", "'true'", "on", "'on'", "t", "'t'" }; + var falsy = new[] { "FALSE", "NO", "'false'", "off", "'off'", "f", "'f'" }; + + if (truthy.Any(x => x.Equals(source.Trim(), StringComparison.OrdinalIgnoreCase))) + { + column.DefaultValue = true; + } + else if (falsy.Any(x => x.Equals(source.Trim(), StringComparison.OrdinalIgnoreCase))) + { + column.DefaultValue = false; + } + else + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.DateTime || column.MigratorDbType == MigratorDbType.DateTime2) + { + if (source.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(source); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + + var timeString = match.Value; + + // We convert to UTC since we restrict date time default values to UTC on default value definition. + var dateTimeExtracted = DateTime.ParseExact(timeString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + + column.DefaultValue = dateTimeExtracted; + } + else + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.Guid) + { + if (source.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(source); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + + column.DefaultValue = Guid.Parse(match.Value); + } + else + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.Binary) + { + if (source.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(source); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + + var singleQuoteString = match.Value; + + if (!singleQuoteString.StartsWith("\\x")) + { + throw new Exception(@"Postgre \x notation expected."); + } + + var hexString = singleQuoteString.Substring(2); + + column.DefaultValue = CatalogLiteralConversions.ParseHex(hexString); + } + else + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + } + else if (column.MigratorDbType == MigratorDbType.DateTimeOffset) + ApplyDateTimeOffset(column, source); + else + { + throw new NotImplementedException($"{nameof(DbType)} {column.MigratorDbType} not implemented."); + } + } + } + private static bool IsNumeric(DbType type) => type is DbType.Int16 or DbType.Int32 or DbType.Int64 + or DbType.UInt16 or DbType.UInt32 or DbType.UInt64 or DbType.Single or DbType.Double or DbType.Decimal; + + private static void ApplyInterval(Column column, string source) + { + if (source.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(source); + + if (!match.Success) + { + throw new Exception("Postgre default value for interval: Single quotes around the interval string are expected."); + } + + var interval = Regex.Match(match.Value, @"^([+-]?)(\d+):(\d{2}):(\d{2}(?:\.\d{1,7})?)$"); + if (!interval.Success) throw new NotSupportedException("Cannot parse interval default: " + source); + var ticks = decimal.Parse(interval.Groups[2].Value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerHour + + decimal.Parse(interval.Groups[3].Value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerMinute + + decimal.Parse(interval.Groups[4].Value, CultureInfo.InvariantCulture) * TimeSpan.TicksPerSecond; + if (interval.Groups[1].Value == "-") ticks = -ticks; + column.DefaultValue = TimeSpan.FromTicks(checked((long)ticks)); + } + else + { + // We assume that the value was added using this migrator so we do not interpret things like '2 days 01:02:03' if you + // added such format you will run into this exception. + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}' unexpected pattern."); + } + } + + private static void ApplyDateTimeOffset(Column column, string source) + { + if (source.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(source); + + if (!match.Success) + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + + var singleQuoteString = match.Value; + + // 1) Normalize "Z" at the end → "+00:00" + singleQuoteString = Regex.Replace(singleQuoteString, @"Z$", "+00:00"); + + // 2) Normalize offset at the end of the string + // Cases handled: + // +HH → +HH:00 + // +HHMM → +HH:MM + // +HH:MM → stays unchanged + // -HH / -HHMM → same logic + singleQuoteString = Regex.Replace( + singleQuoteString, + @"([+-])(\d{2})(?::?(\d{2}))?$", + m => + { + var sign = m.Groups[1].Value; // "+" or "-" + var hh = m.Groups[2].Value; // hours + var hasMm = m.Groups[3].Success; // minutes present? + var mm = hasMm ? m.Groups[3].Value : "00"; + return $"{sign}{hh}:{mm}"; + } + ); + + // 3) Parse using multiple possible formats + // Supports both space and "T" separator, with/without milliseconds + var formats = new[] + { + "yyyy-MM-dd HH:mm:ss.fffzzz", // space separator, with ms + "yyyy-MM-dd HH:mm:sszzz", // space separator, no ms + "yyyy-MM-ddTHH:mm:ss.fffzzz", // ISO8601, with ms + "yyyy-MM-ddTHH:mm:sszzz" // ISO8601, no ms + }; + + var dateTimeOffset = DateTimeOffset.ParseExact( + singleQuoteString, + formats, + CultureInfo.InvariantCulture, + DateTimeStyles.None + ); + + column.DefaultValue = dateTimeOffset; + } + else + { + throw new NotImplementedException($"Cannot parse {source} in column '{column.Name}'"); + } + } +} diff --git a/src/Migrator/Providers/Impl/SqlServer/SqlServerColumnDefault.cs b/src/Migrator/Providers/Impl/SqlServer/SqlServerColumnDefault.cs new file mode 100644 index 00000000..045ec95b --- /dev/null +++ b/src/Migrator/Providers/Impl/SqlServer/SqlServerColumnDefault.cs @@ -0,0 +1,110 @@ +using System; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using DotNetProjects.Migrator.Framework; + +namespace DotNetProjects.Migrator.Providers.Impl.SqlServer; + +internal static class SqlServerColumnDefault +{ + internal static void Apply(Column column, string defaultValueString) + { + if (defaultValueString != null) + { + var bracesStrippedString = defaultValueString.Replace("(", "").Replace(")", "").Trim(); + var bracesAndSingleQuoteStrippedString = bracesStrippedString.Replace("'", ""); + + var parsedDefault = CatalogDefaultValue.Parse(defaultValueString, column.Type); + if (parsedDefault == null) + { + column.DefaultValue = null; + return; + } + if (CatalogLiteralConversions.IsText(column.Type) + || (parsedDefault is RawSql && !System.Text.RegularExpressions.Regex.IsMatch(defaultValueString, + @"(?i)^\(*\s*(CONVERT\s*\(|0x[0-9a-f]+\)*)"))) + column.DefaultValue = parsedDefault; + else if (CatalogLiteralConversions.TryParseNumber(bracesAndSingleQuoteStrippedString, column.Type, preserveSingle: false, out var number)) + column.DefaultValue = number; + else if (column.Type == DbType.Time) + { + column.DefaultValue = TimeOnly.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); + } + else if (column.Type == DbType.Boolean) + { + var truthy = new string[] { "'TRUE'", "1" }; + var falsy = new string[] { "'FALSE'", "0" }; + + if (truthy.Contains(bracesStrippedString)) + { + column.DefaultValue = true; + } + else if (falsy.Contains(bracesStrippedString)) + { + column.DefaultValue = false; + } + else if (bracesStrippedString == "NULL") + { + column.DefaultValue = null; + } + else + { + throw new NotImplementedException($"Cannot parse the boolean default value '{defaultValueString}' of column '{column.Name}'"); + } + } + else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + { + // (CONVERT([datetime],'2000-01-02 03:04:05.000',(121))) + // 121 is a pattern: it contains milliseconds + // Search for 121 here: https://learn.microsoft.com/de-de/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver17 + var regexDateTimeConvert121 = new Regex(@"(?<=^\(CONVERT\([\[]+datetime[\]]+,')[^']+(?='\s*,\s*\(121\s*\)\)\)$)"); + var match121 = regexDateTimeConvert121.Match(defaultValueString); + + if (match121.Success) + { + // We convert to UTC since we restrict date time default values to UTC on default value definition. + column.DefaultValue = DateTime.ParseExact(match121.Value, "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } + else if (defaultValueString is string defVal) + { + // Not tested + var dt = defVal; + if (defVal.StartsWith("'")) + { + dt = defVal.Substring(1, defVal.Length - 2); + } + + // We convert to UTC since we restrict date time default values to UTC on default value definition. + column.DefaultValue = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + } + else + { + throw new NotImplementedException($"Cannot interpret {column.DefaultValue} in column '{column.Name}' unexpected pattern."); + } + } + else if (column.Type == DbType.Guid) + { + column.DefaultValue = Guid.Parse(bracesAndSingleQuoteStrippedString); + } + else if (column.MigratorDbType == MigratorDbType.Binary) + { + if (bracesStrippedString.StartsWith("0x")) + { + var hexString = bracesStrippedString.Substring(2); + + column.DefaultValue = CatalogLiteralConversions.ParseHex(hexString); + } + else + { + throw new NotImplementedException($"Cannot parse the binary default value of '{column.Name}'. The value is '{defaultValueString}'"); + } + } + else + { + throw new NotImplementedException($"Cannot parse the default value of {column.Name} type '{column.MigratorDbType}'. It is not yet implemented - file an issue."); + } + } + } +} diff --git a/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs b/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs index cc9a27ce..9a1ad24f 100644 --- a/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs @@ -617,123 +617,7 @@ public override Column[] GetColumns(string table) column.Size = reader.GetInt32(3); } - if (defaultValueString != null) - { - var bracesStrippedString = defaultValueString.Replace("(", "").Replace(")", "").Trim(); - var bracesAndSingleQuoteStrippedString = bracesStrippedString.Replace("'", ""); - - var parsedDefault = CatalogDefaultValue.Parse(defaultValueString, column.Type); - if (column.Type is DbType.String or DbType.AnsiString or DbType.StringFixedLength or DbType.AnsiStringFixedLength - || (parsedDefault is RawSql && !System.Text.RegularExpressions.Regex.IsMatch(defaultValueString, - @"(?i)^\(*\s*(CONVERT\s*\(|0x[0-9a-f]+\)*)"))) - column.DefaultValue = parsedDefault; - else if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) - { - column.DefaultValue = long.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) - { - column.DefaultValue = ulong.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Double || column.Type == DbType.Single) - { - column.DefaultValue = double.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Time) - { - column.DefaultValue = TimeOnly.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.Type == DbType.Boolean) - { - var truthy = new string[] { "'TRUE'", "1" }; - var falsy = new string[] { "'FALSE'", "0" }; - - if (truthy.Contains(bracesStrippedString)) - { - column.DefaultValue = true; - } - else if (falsy.Contains(bracesStrippedString)) - { - column.DefaultValue = false; - } - else if (bracesStrippedString == "NULL") - { - column.DefaultValue = null; - } - else - { - throw new NotImplementedException($"Cannot parse the boolean default value '{defaultValueString}' of column '{column.Name}'"); - } - } - else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) - { - // (CONVERT([datetime],'2000-01-02 03:04:05.000',(121))) - // 121 is a pattern: it contains milliseconds - // Search for 121 here: https://learn.microsoft.com/de-de/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver17 - var regexDateTimeConvert121 = new Regex(@"(?<=^\(CONVERT\([\[]+datetime[\]]+,')[^']+(?='\s*,\s*\(121\s*\)\)\)$)"); - var match121 = regexDateTimeConvert121.Match(defaultValueString); - - if (match121.Success) - { - // We convert to UTC since we restrict date time default values to UTC on default value definition. - column.DefaultValue = DateTime.ParseExact(match121.Value, "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); - } - else if (defaultValueString is string defVal) - { - // Not tested - var dt = defVal; - if (defVal.StartsWith("'")) - { - dt = defVal.Substring(1, defVal.Length - 2); - } - - // We convert to UTC since we restrict date time default values to UTC on default value definition. - column.DefaultValue = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); - } - else - { - throw new NotImplementedException($"Cannot interpret {column.DefaultValue} in column '{column.Name}' unexpected pattern."); - } - } - else if (column.Type == DbType.Guid) - { - column.DefaultValue = Guid.Parse(bracesAndSingleQuoteStrippedString); - } - else if (column.MigratorDbType == MigratorDbType.Decimal) - { - // We assume ((1.234)) - column.DefaultValue = decimal.Parse(bracesAndSingleQuoteStrippedString, CultureInfo.InvariantCulture); - } - else if (column.MigratorDbType == MigratorDbType.String) - { - column.DefaultValue = bracesAndSingleQuoteStrippedString; - } - else if (column.MigratorDbType == MigratorDbType.Binary) - { - if (bracesStrippedString.StartsWith("0x")) - { - var hexString = bracesStrippedString.Substring(2); - - // Not available in old .NET version: Convert.FromHexString(hexString); - - column.DefaultValue = Enumerable.Range(0, hexString.Length / 2) - .Select(x => Convert.ToByte(hexString.Substring(x * 2, 2), 16)) - .ToArray(); - } - else - { - throw new NotImplementedException($"Cannot parse the binary default value of '{column.Name}'. The value is '{defaultValueString}'"); - } - } - else if (column.MigratorDbType == MigratorDbType.Byte) - { - column.DefaultValue = byte.Parse(bracesAndSingleQuoteStrippedString); - } - else - { - throw new NotImplementedException($"Cannot parse the default value of {column.Name} type '{column.MigratorDbType}'. It is not yet implemented - file an issue."); - } - } + SqlServerColumnDefault.Apply(column, defaultValueString); if (!reader.IsDBNull(5)) { if (column.Type == DbType.Decimal) diff --git a/src/Migrator/Providers/TypeNames.cs b/src/Migrator/Providers/TypeNames.cs index 8f0944d0..16c48a9b 100644 --- a/src/Migrator/Providers/TypeNames.cs +++ b/src/Migrator/Providers/TypeNames.cs @@ -56,25 +56,18 @@ public class TypeNames public DbType GetDbType(string type) { - type = type.Trim().ToLower(); - var retval = defaults.Where(x => x.Value.Trim().ToLower().StartsWith(type)).Select(x => x.Key); - if (retval.Any()) - { - return (DbType)retval.First(); - } - - retval = weighted.Where(x => x.Value.Where(y => y.Value.Trim().ToLower().StartsWith(type)).Any()).Select(x => x.Key); - if (retval.Any()) - { - return (DbType)retval.First(); - } + type = type.Trim(); + foreach (var entry in defaults) + if (entry.Value.Trim().StartsWith(type, StringComparison.OrdinalIgnoreCase)) + return (DbType)entry.Key; - var alias = aliases.Where(x => x.Key.Trim().ToLower().StartsWith(type)); + foreach (var entry in weighted) + if (entry.Value.Values.Any(value => value.Trim().StartsWith(type, StringComparison.OrdinalIgnoreCase))) + return (DbType)entry.Key; - if (alias.Any()) - { - return (DbType)alias.First().Value; - } + foreach (var entry in aliases) + if (entry.Key.Trim().StartsWith(type, StringComparison.OrdinalIgnoreCase)) + return (DbType)entry.Value; return DbType.AnsiString; } @@ -135,7 +128,7 @@ public string Get(DbType typecode, int size, int precision, int scale) } } //Could not find a specific type for the size, using the default - return Get(typecode); + return Replace(Get(typecode), size, precision, scale); } private static string Replace(string type, int size, int precision, int scale) @@ -153,13 +146,7 @@ private static string Replace(string type, int size, int precision, int scale) /// The associated name public void Put(DbType typecode, int capacity, string value) { - SortedList map; - if (!weighted.TryGetValue((MigratorDbType)typecode, out map)) - { - // add new ordered map - weighted[(MigratorDbType)typecode] = map = new SortedList(); - } - map[capacity] = value; + Put((MigratorDbType)typecode, capacity, value); } /// @@ -186,7 +173,7 @@ public void Put(MigratorDbType typecode, int capacity, string value) /// public void Put(DbType typecode, string value) { - defaults[(MigratorDbType)typecode] = value; + Put((MigratorDbType)typecode, value); } ///