From 6aedd59c5d9e75738cd30919ebaa68557f8c1dc1 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 10:30:09 +0200 Subject: [PATCH 01/17] Preparations for Default Value tests --- ...ionProvider_GetColumnsDefaultValueTests.cs | 47 +++++++++++++++++++ .../PostgreSQLTransformationProvider.cs | 14 +++--- 2 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs new file mode 100644 index 00000000..8df46b5b --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using Npgsql; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_GetColumnsDefaultValueTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void AddTableWithPrimaryKeyIdentity_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + + Provider.AddTable(testTableName, + new Column(propertyName1, DbType.DateTime2, new DateTime(1980, 1, 1)), + new Column(propertyName2, DbType.Decimal) + ); + + // Act + var column = Provider.GetColumns(testTableName).Single(x => x.Name.Equals(propertyName1, StringComparison.OrdinalIgnoreCase)); + Provider.Insert(testTableName, [propertyName2], [3.448484]); + + // Assert + // using (var command = Provider.GetCommand()) + // { + // using var reader = Provider.ExecuteQuery(command, $"SELECT max({propertyName1}) as max from {testTableName}"); + // reader.Read(); + + // var primaryKeyValue = reader.GetInt32(reader.GetOrdinal("max")); + // Assert.That(primaryKeyValue, Is.EqualTo(2)); + // } + + // // Act II + // var exception = Assert.Throws(() => Provider.Insert(testTableName, [propertyName1, propertyName2], [1, 888])); + + // // Assert II + // Assert.That(exception.SqlState, Is.EqualTo("428C9")); + } +} diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index c6f391ab..dd0dc6e4 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -246,18 +246,17 @@ public override string[] GetTables() public override Column[] GetColumns(string table) { + var query = $"SELECT COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'public' AND TABLE_NAME = lower('{table}');"; + var columns = new List(); + using (var cmd = CreateCommand()) - using ( - var reader = - ExecuteQuery(cmd, - string.Format("select COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT from information_schema.columns where table_schema = 'public' AND table_name = lower('{0}');", table))) + using (var reader = ExecuteQuery(cmd, query)) { - // FIXME: Mostly duplicated code from the Transformation provider just to support stupid case-insensitivty of Postgre while (reader.Read()) { var column = new Column(reader[0].ToString(), DbType.String); - var isNullable = reader.GetString(1) == "YES"; + var isNullable = reader.GetString(reader.GetOrdinal("IS_NULLABLE")) == "YES"; var defaultValue = reader.GetValue(2); column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; @@ -283,7 +282,7 @@ public override Column[] GetColumns(string table) } else if (column.Type == DbType.Boolean) { - column.DefaultValue = column.DefaultValue.ToString().Trim() == "1" || column.DefaultValue.ToString().Trim().ToUpper() == "TRUE" || column.DefaultValue.ToString().Trim() == "YES"; + column.DefaultValue = column.DefaultValue.ToString().Trim() == "1" || column.DefaultValue.ToString().Trim().Equals("TRUE", StringComparison.OrdinalIgnoreCase) || column.DefaultValue.ToString().Trim() == "YES"; } else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) { @@ -304,6 +303,7 @@ public override Column[] GetColumns(string table) if (column.DefaultValue is string defVal) { var dt = defVal; + if (defVal.StartsWith("'")) { dt = defVal.Substring(1, defVal.Length - 2); From ec13b6c37e53a9eefb6c80c384a0148e9bad0f2a Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 10:36:18 +0200 Subject: [PATCH 02/17] Preparations for Type tests. --- ...ionProvider_GetColumnsDefaultValueTests.cs | 2 ++ ...nsformationProvider_GetColumnsTypeTests.cs | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 8df46b5b..662504d6 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -43,5 +43,7 @@ public void AddTableWithPrimaryKeyIdentity_Succeeds() // // Assert II // Assert.That(exception.SqlState, Is.EqualTo("428C9")); + + throw new NotImplementedException(); } } diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs new file mode 100644 index 00000000..97d7cfe3 --- /dev/null +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -0,0 +1,31 @@ +using System; +using System.Data; +using System.Linq; +using DotNetProjects.Migrator.Framework; +using NUnit.Framework; + +namespace Migrator.Tests.Providers.PostgreSQL; + +[TestFixture] +[Category("Postgre")] +public class PostgreSQLTransformationProvider_GetColumnsTypeTests : PostgreSQLTransformationProviderTestBase +{ + [Test] + public void AddTableWithPrimaryKeyIdentity_Succeeds() + { + // Arrange + const string testTableName = "MyDefaultTestTable"; + const string propertyName1 = "Color1"; + const string propertyName2 = "Color2"; + + Provider.AddTable(testTableName, + new Column(propertyName2, DbType.Decimal) + ); + + // Act + var column = Provider.GetColumns(testTableName).Single(x => x.Name.Equals(propertyName1, StringComparison.OrdinalIgnoreCase)); + Provider.Insert(testTableName, [propertyName2], [3.448484]); + + throw new NotImplementedException(); + } +} From e7464e6c3f4b4fe3e5598d331036c2287e91c0b1 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 12:09:14 +0200 Subject: [PATCH 03/17] Map postgre data type to DbType --- ...ionProvider_GetColumnsDefaultValueTests.cs | 2 +- .../PostgreSQLTransformationProvider.cs | 124 +++++++++++++++++- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 662504d6..6fbcdebb 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -12,7 +12,7 @@ namespace Migrator.Tests.Providers.PostgreSQL; public class PostgreSQLTransformationProvider_GetColumnsDefaultValueTests : PostgreSQLTransformationProviderTestBase { [Test] - public void AddTableWithPrimaryKeyIdentity_Succeeds() + public void GetColumns_DataTypeResolveSucceeds() { // Arrange const string testTableName = "MyDefaultTestTable"; diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index dd0dc6e4..a0658401 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -16,6 +16,8 @@ using System.Collections.Generic; using System.Data; using System.Globalization; +using System.Linq; +using System.Text; using Index = DotNetProjects.Migrator.Framework.Index; namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; @@ -246,25 +248,133 @@ public override string[] GetTables() public override Column[] GetColumns(string table) { - var query = $"SELECT COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'public' AND TABLE_NAME = lower('{table}');"; + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("SELECT"); + stringBuilder.AppendLine(" COLUMN_NAME,"); + stringBuilder.AppendLine(" IS_NULLABLE,"); + stringBuilder.AppendLine(" COLUMN_DEFAULT,"); + stringBuilder.AppendLine(" DATA_TYPE,"); + stringBuilder.AppendLine(" DATETIME_PRECISION,"); + stringBuilder.AppendLine(" CHARACTER_MAXIMUM_LENGTH"); + stringBuilder.AppendLine($"FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'public' AND TABLE_NAME = lower('{table}');"); var columns = new List(); using (var cmd = CreateCommand()) - using (var reader = ExecuteQuery(cmd, query)) + using (var reader = ExecuteQuery(cmd, stringBuilder.ToString())) { while (reader.Read()) { - var column = new Column(reader[0].ToString(), DbType.String); + var columnName = reader.GetString(reader.GetOrdinal("COLUMN_NAME")); var isNullable = reader.GetString(reader.GetOrdinal("IS_NULLABLE")) == "YES"; - var defaultValue = reader.GetValue(2); + var defaultValueString = reader.GetString(reader.GetOrdinal("COLUMN_DEFAULT")); + var dataTypeString = reader.GetString(reader.GetOrdinal("DATA_TYPE")); + var dateTimePrecision = reader.GetInt32(reader.GetOrdinal("DATETIME_PRECISION")); + var characterMaximumLength = reader.GetInt32(reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH")); - column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + DbType dbType = 0; + int? precision = null; + int? size = null; + + if (new[] { "timestamptz", "timestamp with timezone" }.Contains(dataTypeString)) + { + dbType = DbType.DateTimeOffset; + precision = dateTimePrecision; + } + else if (dataTypeString == "timestamp") + { + if (dateTimePrecision > 6) + { + dbType = DbType.DateTime2; + } + else + { + dbType = DbType.DateTime; + } - if (defaultValue != null && defaultValue != DBNull.Value) + precision = dateTimePrecision; + } + else if (dataTypeString == "smallint") + { + dbType = DbType.Int16; + } + else if (dataTypeString == "integer") + { + dbType = DbType.Int32; + } + else if (dataTypeString == "bigint") + { + dbType = DbType.Int64; + } + else if (dataTypeString == "numeric") + { + dbType = DbType.Decimal; + } + else if (dataTypeString == "real") + { + dbType = DbType.Single; + } + else if (dataTypeString == "money") + { + dbType = DbType.Currency; + } + else if (dataTypeString == "date") + { + dbType = DbType.Date; + } + else if (dataTypeString == "byte") { - column.DefaultValue = defaultValue; + dbType = DbType.Binary; } + else if (dataTypeString == "uuid") + { + dbType = DbType.Guid; + } + else if (dataTypeString == "xml") + { + dbType = DbType.Xml; + } + else if (dataTypeString == "time") + { + dbType = DbType.Time; + } + else if (dataTypeString == "interval") + { + throw new NotImplementedException(); + } + else if (dataTypeString == "boolean") + { + dbType = DbType.Boolean; + } + else if (dataTypeString == "text") + { + dbType = DbType.String; + } + else if (dataTypeString.StartsWith("character varying(")) + { + dbType = DbType.StringFixedLength; + size = characterMaximumLength; + } + else if (dataTypeString == "character" || dataTypeString.StartsWith("character(")) + { + throw new NotSupportedException("Data type 'character' detected. We do not support 'character'. Use 'text' or 'character varying(n)' instead"); + } + else + { + throw new NotImplementedException("The data type is not implemented. Please file an issue."); + } + + var column = new Column(columnName, dbType) + { + Precision = precision + }; + + column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; + + // if (defaultValueString != null && defaultValueString != DBNull.Value) + // { + // column.DefaultValue = defaultValueString; + // } if (column.DefaultValue != null) { From 65d11821f162217892c7eec43eb95eb4249570da Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 13:44:37 +0200 Subject: [PATCH 04/17] Updated reader for type --- ...ormationProvider_GetColumnsDefaultValueTests.cs | 14 ++++++++------ .../PostgreSQL/PostgreSQLTransformationProvider.cs | 12 ++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 6fbcdebb..71044aad 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -16,17 +16,19 @@ public void GetColumns_DataTypeResolveSucceeds() { // Arrange const string testTableName = "MyDefaultTestTable"; - const string propertyName1 = "Color1"; - const string propertyName2 = "Color2"; + const string dateTimeColumnName = "datetimecolumn"; + const string decimalColumnName = "decimalcolumn"; Provider.AddTable(testTableName, - new Column(propertyName1, DbType.DateTime2, new DateTime(1980, 1, 1)), - new Column(propertyName2, DbType.Decimal) + new Column(dateTimeColumnName, DbType.DateTime2), + new Column(decimalColumnName, DbType.Decimal) ); // Act - var column = Provider.GetColumns(testTableName).Single(x => x.Name.Equals(propertyName1, StringComparison.OrdinalIgnoreCase)); - Provider.Insert(testTableName, [propertyName2], [3.448484]); + var dateTimeColumn = Provider.GetColumns(testTableName).Single(x => x.Name.Equals(dateTimeColumnName, StringComparison.OrdinalIgnoreCase)); + + // Assert + Assert.That(dateTimeColumn.Type, Is.EqualTo(DbType.DateTime2)); // Assert // using (var command = Provider.GetCommand()) diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index a0658401..78423df1 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -265,18 +265,22 @@ public override Column[] GetColumns(string table) { while (reader.Read()) { + var defaultValueOrdinal = reader.GetOrdinal("COLUMN_DEFAULT"); + var characterMaximumLengthOrdinal = reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH"); + var dateTimePrecisionOrdinal = reader.GetOrdinal("DATETIME_PRECISION"); + var columnName = reader.GetString(reader.GetOrdinal("COLUMN_NAME")); var isNullable = reader.GetString(reader.GetOrdinal("IS_NULLABLE")) == "YES"; - var defaultValueString = reader.GetString(reader.GetOrdinal("COLUMN_DEFAULT")); + var defaultValueString = reader.IsDBNull(defaultValueOrdinal) ? null : reader.GetString(defaultValueOrdinal); var dataTypeString = reader.GetString(reader.GetOrdinal("DATA_TYPE")); - var dateTimePrecision = reader.GetInt32(reader.GetOrdinal("DATETIME_PRECISION")); - var characterMaximumLength = reader.GetInt32(reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH")); + var dateTimePrecision = reader.IsDBNull(dateTimePrecisionOrdinal) ? null : (int?)reader.GetInt32(dateTimePrecisionOrdinal); + var characterMaximumLength = reader.IsDBNull(characterMaximumLengthOrdinal) ? null : (int?)reader.GetInt32(characterMaximumLengthOrdinal); DbType dbType = 0; int? precision = null; int? size = null; - if (new[] { "timestamptz", "timestamp with timezone" }.Contains(dataTypeString)) + if (new[] { "timestamptz", "timestamp with time zone" }.Contains(dataTypeString)) { dbType = DbType.DateTimeOffset; precision = dateTimePrecision; From d4f3144860ac1f7f7e52b816f518c50a93a935b0 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 14:21:14 +0200 Subject: [PATCH 05/17] Added DateTime2, DateTime and Decimal --- ...ionProvider_GetColumnsDefaultValueTests.cs | 24 ++++++++++++++----- .../Impl/PostgreSQL/PostgreSQLDialect.cs | 9 +++++-- .../PostgreSQLTransformationProvider.cs | 5 ++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 71044aad..d02082b8 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -16,19 +16,31 @@ public void GetColumns_DataTypeResolveSucceeds() { // Arrange const string testTableName = "MyDefaultTestTable"; - const string dateTimeColumnName = "datetimecolumn"; - const string decimalColumnName = "decimalcolumn"; + const string dateTimeColumnName1 = "datetimecolumn1"; + const string dateTimeColumnName2 = "datetimecolumn2"; + const string decimalColumnName1 = "decimalcolumn"; Provider.AddTable(testTableName, - new Column(dateTimeColumnName, DbType.DateTime2), - new Column(decimalColumnName, DbType.Decimal) + new Column(dateTimeColumnName1, DbType.DateTime), + new Column(dateTimeColumnName2, DbType.DateTime2), + new Column(decimalColumnName1, DbType.Decimal) ); // Act - var dateTimeColumn = Provider.GetColumns(testTableName).Single(x => x.Name.Equals(dateTimeColumnName, StringComparison.OrdinalIgnoreCase)); + var columns = Provider.GetColumns(testTableName); + + var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); + var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); + var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); // Assert - Assert.That(dateTimeColumn.Type, Is.EqualTo(DbType.DateTime2)); + Assert.That(dateTimeColumn1.Type, Is.EqualTo(DbType.DateTime)); + Assert.That(dateTimeColumn1.Precision, Is.EqualTo(3)); + + Assert.That(dateTimeColumn2.Type, Is.EqualTo(DbType.DateTime2)); + Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); + + Assert.That(decimalColumn1.Type, Is.EqualTo(DbType.Decimal)); // Assert // using (var command = Provider.GetCommand()) diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs index 9923db1f..07d00246 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs @@ -18,8 +18,13 @@ public PostgreSQLDialect() RegisterColumnType(DbType.Byte, "int2"); RegisterColumnType(DbType.Currency, "decimal(16,4)"); RegisterColumnType(DbType.Date, "date"); - RegisterColumnType(DbType.DateTime, "timestamptz"); - RegisterColumnType(DbType.DateTime2, "timestamptz"); + + // 8 bytes - resolution 1 microsecond + RegisterColumnType(DbType.DateTime, "timestamp(3)"); + + // 8 bytes - resolution 1 microsecond + // We do not use timezone any more - this is near a datetime2 in SQL Server + RegisterColumnType(DbType.DateTime2, "timestamp(6)"); RegisterColumnType(DbType.DateTimeOffset, "timestamptz"); RegisterColumnType(DbType.Decimal, "decimal(19,5)"); RegisterColumnType(DbType.Decimal, 19, "decimal(18, $l)"); diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 78423df1..f6ab07ca 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -285,9 +285,10 @@ public override Column[] GetColumns(string table) dbType = DbType.DateTimeOffset; precision = dateTimePrecision; } - else if (dataTypeString == "timestamp") + else if (dataTypeString == "timestamp" || dataTypeString == "timestamp without time zone") { - if (dateTimePrecision > 6) + // 6 is the maximum in PostgreSQL + if (dateTimePrecision > 5) { dbType = DbType.DateTime2; } From 6c887c372f8e2bbef10fbf63b9d6f136e947c525 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 14:26:58 +0200 Subject: [PATCH 06/17] Added numeric precision and scale to query --- ...SQLTransformationProvider_GetColumnsDefaultValueTests.cs | 1 - .../Impl/PostgreSQL/PostgreSQLTransformationProvider.cs | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index d02082b8..4c5c61a4 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -2,7 +2,6 @@ using System.Data; using System.Linq; using DotNetProjects.Migrator.Framework; -using Npgsql; using NUnit.Framework; namespace Migrator.Tests.Providers.PostgreSQL; diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index f6ab07ca..6755ac61 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -255,7 +255,9 @@ public override Column[] GetColumns(string table) stringBuilder.AppendLine(" COLUMN_DEFAULT,"); stringBuilder.AppendLine(" DATA_TYPE,"); stringBuilder.AppendLine(" DATETIME_PRECISION,"); - stringBuilder.AppendLine(" CHARACTER_MAXIMUM_LENGTH"); + stringBuilder.AppendLine(" CHARACTER_MAXIMUM_LENGTH,"); + stringBuilder.AppendLine(" NUMERIC_PRECISION,"); + stringBuilder.AppendLine(" NUMERIC_SCALE,"); stringBuilder.AppendLine($"FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'public' AND TABLE_NAME = lower('{table}');"); var columns = new List(); @@ -268,6 +270,8 @@ public override Column[] GetColumns(string table) var defaultValueOrdinal = reader.GetOrdinal("COLUMN_DEFAULT"); var characterMaximumLengthOrdinal = reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH"); var dateTimePrecisionOrdinal = reader.GetOrdinal("DATETIME_PRECISION"); + var numericPrecisionOrdinal = reader.GetOrdinal("NUMERIC_PRECISION"); + var numericScaleOrdinal = reader.GetOrdinal("NUMERIC_SCALE"); var columnName = reader.GetString(reader.GetOrdinal("COLUMN_NAME")); var isNullable = reader.GetString(reader.GetOrdinal("IS_NULLABLE")) == "YES"; From 886c45404344aef927bb714dd50c4aeefe8b5529 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 15:26:02 +0200 Subject: [PATCH 07/17] Extended type test for Postgre SQL --- ...ionProvider_GetColumnsDefaultValueTests.cs | 31 +++++++++---------- src/Migrator/Framework/Column.cs | 6 ++++ .../PostgreSQLTransformationProvider.cs | 11 +++++-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 4c5c61a4..e4fd351c 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -18,11 +18,17 @@ public void GetColumns_DataTypeResolveSucceeds() const string dateTimeColumnName1 = "datetimecolumn1"; const string dateTimeColumnName2 = "datetimecolumn2"; const string decimalColumnName1 = "decimalcolumn"; + const string guidColumnName1 = "guidcolumn1"; + const string booleanColumnName1 = "booleancolumn1"; + const string int32ColumnName1 = "int32column1"; Provider.AddTable(testTableName, new Column(dateTimeColumnName1, DbType.DateTime), new Column(dateTimeColumnName2, DbType.DateTime2), - new Column(decimalColumnName1, DbType.Decimal) + new Column(decimalColumnName1, DbType.Decimal), + new Column(guidColumnName1, DbType.Guid), + new Column(booleanColumnName1, DbType.Boolean), + new Column(int32ColumnName1, DbType.Int32) ); // Act @@ -31,6 +37,9 @@ public void GetColumns_DataTypeResolveSucceeds() var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); + var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); + var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); + var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); // Assert Assert.That(dateTimeColumn1.Type, Is.EqualTo(DbType.DateTime)); @@ -40,23 +49,13 @@ public void GetColumns_DataTypeResolveSucceeds() Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); Assert.That(decimalColumn1.Type, Is.EqualTo(DbType.Decimal)); + Assert.That(decimalColumn1.Precision, Is.EqualTo(19)); + Assert.That(decimalColumn1.Scale, Is.EqualTo(5)); - // Assert - // using (var command = Provider.GetCommand()) - // { - // using var reader = Provider.ExecuteQuery(command, $"SELECT max({propertyName1}) as max from {testTableName}"); - // reader.Read(); - - // var primaryKeyValue = reader.GetInt32(reader.GetOrdinal("max")); - // Assert.That(primaryKeyValue, Is.EqualTo(2)); - // } - - // // Act II - // var exception = Assert.Throws(() => Provider.Insert(testTableName, [propertyName1, propertyName2], [1, 888])); + Assert.That(guidColumn1.Type, Is.EqualTo(DbType.Guid)); - // // Assert II - // Assert.That(exception.SqlState, Is.EqualTo("428C9")); + Assert.That(booleanColumn1.Type, Is.EqualTo(DbType.Boolean)); - throw new NotImplementedException(); + Assert.That(int32Column1.Type, Is.EqualTo(DbType.Int32)); } } diff --git a/src/Migrator/Framework/Column.cs b/src/Migrator/Framework/Column.cs index 6d497bc3..e57fa61f 100644 --- a/src/Migrator/Framework/Column.cs +++ b/src/Migrator/Framework/Column.cs @@ -147,8 +147,14 @@ public DbType Type public int Size { get; set; } + /// + /// Gets or sets the precision for NUMERIC/DECIMAL + /// public int? Precision { get; set; } + /// + /// Gets or sets the scale for NUMERIC/DECIMAL + /// public int? Scale { get; set; } public ColumnProperty ColumnProperty { get; set; } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 6755ac61..88e9a0d6 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -17,6 +17,7 @@ using System.Data; using System.Globalization; using System.Linq; +using System.Reflection.Metadata.Ecma335; using System.Text; using Index = DotNetProjects.Migrator.Framework.Index; @@ -257,7 +258,7 @@ public override Column[] GetColumns(string table) stringBuilder.AppendLine(" DATETIME_PRECISION,"); stringBuilder.AppendLine(" CHARACTER_MAXIMUM_LENGTH,"); stringBuilder.AppendLine(" NUMERIC_PRECISION,"); - stringBuilder.AppendLine(" NUMERIC_SCALE,"); + stringBuilder.AppendLine(" NUMERIC_SCALE"); stringBuilder.AppendLine($"FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'public' AND TABLE_NAME = lower('{table}');"); var columns = new List(); @@ -279,9 +280,12 @@ public override Column[] GetColumns(string table) var dataTypeString = reader.GetString(reader.GetOrdinal("DATA_TYPE")); var dateTimePrecision = reader.IsDBNull(dateTimePrecisionOrdinal) ? null : (int?)reader.GetInt32(dateTimePrecisionOrdinal); var characterMaximumLength = reader.IsDBNull(characterMaximumLengthOrdinal) ? null : (int?)reader.GetInt32(characterMaximumLengthOrdinal); + var numericPrecision = reader.IsDBNull(numericPrecisionOrdinal) ? null : (int?)reader.GetInt32(numericPrecisionOrdinal); + var numericScale = reader.IsDBNull(numericScaleOrdinal) ? null : (int?)reader.GetInt32(numericScaleOrdinal); DbType dbType = 0; int? precision = null; + int? scale = null; int? size = null; if (new[] { "timestamptz", "timestamp with time zone" }.Contains(dataTypeString)) @@ -318,6 +322,8 @@ public override Column[] GetColumns(string table) else if (dataTypeString == "numeric") { dbType = DbType.Decimal; + precision = numericPrecision; + scale = numericScale; } else if (dataTypeString == "real") { @@ -375,7 +381,8 @@ public override Column[] GetColumns(string table) var column = new Column(columnName, dbType) { - Precision = precision + Precision = precision, + Scale = scale }; column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; From c6d4d275f24791bffd178ec8e35317ea4e9e59d3 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 16:11:53 +0200 Subject: [PATCH 08/17] Extended type test --- ...ionProvider_GetColumnsDefaultValueTests.cs | 21 +++++++++++++------ .../PostgreSQLTransformationProvider.cs | 13 +++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index e4fd351c..6ac5de05 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -21,6 +21,9 @@ public void GetColumns_DataTypeResolveSucceeds() const string guidColumnName1 = "guidcolumn1"; const string booleanColumnName1 = "booleancolumn1"; const string int32ColumnName1 = "int32column1"; + const string int64ColumnName1 = "int64column1"; + const string stringColumnName1 = "stringcolumn1"; + const string stringColumnName2 = "stringcolumn2"; Provider.AddTable(testTableName, new Column(dateTimeColumnName1, DbType.DateTime), @@ -28,7 +31,10 @@ public void GetColumns_DataTypeResolveSucceeds() new Column(decimalColumnName1, DbType.Decimal), new Column(guidColumnName1, DbType.Guid), new Column(booleanColumnName1, DbType.Boolean), - new Column(int32ColumnName1, DbType.Int32) + new Column(int32ColumnName1, DbType.Int32), + new Column(int64ColumnName1, DbType.Int64), + new Column(stringColumnName1, DbType.String), + new Column(stringColumnName2, DbType.String) { Size = 30 } ); // Act @@ -40,22 +46,25 @@ public void GetColumns_DataTypeResolveSucceeds() var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); + var int64column1 = columns.Single(x => x.Name == int64ColumnName1); + var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); + var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); + // Assert Assert.That(dateTimeColumn1.Type, Is.EqualTo(DbType.DateTime)); Assert.That(dateTimeColumn1.Precision, Is.EqualTo(3)); - Assert.That(dateTimeColumn2.Type, Is.EqualTo(DbType.DateTime2)); Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); - Assert.That(decimalColumn1.Type, Is.EqualTo(DbType.Decimal)); Assert.That(decimalColumn1.Precision, Is.EqualTo(19)); Assert.That(decimalColumn1.Scale, Is.EqualTo(5)); - Assert.That(guidColumn1.Type, Is.EqualTo(DbType.Guid)); - Assert.That(booleanColumn1.Type, Is.EqualTo(DbType.Boolean)); - Assert.That(int32Column1.Type, Is.EqualTo(DbType.Int32)); + Assert.That(int64column1.Type, Is.EqualTo(DbType.Int64)); + Assert.That(stringColumn1.Type, Is.EqualTo(DbType.String)); + Assert.That(stringColumn2.Type, Is.EqualTo(DbType.String)); + Assert.That(stringColumn2.Size, Is.EqualTo(30)); } } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 88e9a0d6..9874bc7c 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -17,7 +17,6 @@ using System.Data; using System.Globalization; using System.Linq; -using System.Reflection.Metadata.Ecma335; using System.Text; using Index = DotNetProjects.Migrator.Framework.Index; @@ -361,18 +360,14 @@ public override Column[] GetColumns(string table) { dbType = DbType.Boolean; } - else if (dataTypeString == "text") + else if (dataTypeString == "text" || dataTypeString == "character varying") { dbType = DbType.String; - } - else if (dataTypeString.StartsWith("character varying(")) - { - dbType = DbType.StringFixedLength; size = characterMaximumLength; } else if (dataTypeString == "character" || dataTypeString.StartsWith("character(")) { - throw new NotSupportedException("Data type 'character' detected. We do not support 'character'. Use 'text' or 'character varying(n)' instead"); + throw new NotSupportedException("Data type 'character' detected. We do not support 'character'. Use 'text' or 'character varying' instead"); } else { @@ -382,7 +377,9 @@ public override Column[] GetColumns(string table) var column = new Column(columnName, dbType) { Precision = precision, - Scale = scale + Scale = scale, + // Size should be nullable + Size = size ?? 0 }; column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; From 1499598bbea3f43da50e51e9f6f3e031d4ac4179 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 17:05:32 +0200 Subject: [PATCH 09/17] Throw if default value is DateTime but not UTC --- src/Migrator/Framework/Column.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Migrator/Framework/Column.cs b/src/Migrator/Framework/Column.cs index e57fa61f..765a98ac 100644 --- a/src/Migrator/Framework/Column.cs +++ b/src/Migrator/Framework/Column.cs @@ -11,6 +11,7 @@ #endregion +using System; using System.Data; namespace DotNetProjects.Migrator.Framework; @@ -20,6 +21,8 @@ namespace DotNetProjects.Migrator.Framework; /// public class Column : IColumn, IDbField { + private object _defaultValue; + public Column(string name) { Name = name; @@ -159,7 +162,22 @@ public DbType Type public ColumnProperty ColumnProperty { get; set; } - public object DefaultValue { get; set; } + public object DefaultValue + { + get => _defaultValue; + set + { + if (value is DateTime defaultValueDateTime) + { + if (defaultValueDateTime.Kind != DateTimeKind.Utc) + { + throw new Exception("We only accept UTC values as default DateTime values."); + } + } + + _defaultValue = value; + } + } public bool IsIdentity { From 00600491f7434be50f9cfbc2723abddf577682ce Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 17:06:31 +0200 Subject: [PATCH 10/17] Added default DateTime value resolution --- ...ionProvider_GetColumnsDefaultValueTests.cs | 54 +++++++----------- ...nsformationProvider_GetColumnsTypeTests.cs | 57 ++++++++++++++++--- .../PostgreSQLTransformationProvider.cs | 30 ++++++---- 3 files changed, 85 insertions(+), 56 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 6ac5de05..b9147172 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -8,7 +8,7 @@ namespace Migrator.Tests.Providers.PostgreSQL; [TestFixture] [Category("Postgre")] -public class PostgreSQLTransformationProvider_GetColumnsDefaultValueTests : PostgreSQLTransformationProviderTestBase +public class PostgreSQLTransformationProvider_GetColumnsDefaultTypeTests : PostgreSQLTransformationProviderTestBase { [Test] public void GetColumns_DataTypeResolveSucceeds() @@ -25,46 +25,30 @@ public void GetColumns_DataTypeResolveSucceeds() const string stringColumnName1 = "stringcolumn1"; const string stringColumnName2 = "stringcolumn2"; + // Should be extended by remaining types Provider.AddTable(testTableName, - new Column(dateTimeColumnName1, DbType.DateTime), - new Column(dateTimeColumnName2, DbType.DateTime2), - new Column(decimalColumnName1, DbType.Decimal), - new Column(guidColumnName1, DbType.Guid), - new Column(booleanColumnName1, DbType.Boolean), - new Column(int32ColumnName1, DbType.Int32), - new Column(int64ColumnName1, DbType.Int64), - new Column(stringColumnName1, DbType.String), - new Column(stringColumnName2, DbType.String) { Size = 30 } + new Column(dateTimeColumnName1, DbType.DateTime, new DateTime(2000, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)), + new Column(dateTimeColumnName2, DbType.DateTime2, new DateTime(2000, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)) + // new Column(decimalColumnName1, DbType.Decimal), + // new Column(guidColumnName1, DbType.Guid), + // new Column(booleanColumnName1, DbType.Boolean), + // new Column(int32ColumnName1, DbType.Int32), + // new Column(int64ColumnName1, DbType.Int64), + // new Column(stringColumnName1, DbType.String), + // new Column(stringColumnName2, DbType.String) { Size = 30 } ); // Act var columns = Provider.GetColumns(testTableName); var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); - var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); - var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); - var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); - var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); - var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); - var int64column1 = columns.Single(x => x.Name == int64ColumnName1); - var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); - var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); - - - // Assert - Assert.That(dateTimeColumn1.Type, Is.EqualTo(DbType.DateTime)); - Assert.That(dateTimeColumn1.Precision, Is.EqualTo(3)); - Assert.That(dateTimeColumn2.Type, Is.EqualTo(DbType.DateTime2)); - Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); - Assert.That(decimalColumn1.Type, Is.EqualTo(DbType.Decimal)); - Assert.That(decimalColumn1.Precision, Is.EqualTo(19)); - Assert.That(decimalColumn1.Scale, Is.EqualTo(5)); - Assert.That(guidColumn1.Type, Is.EqualTo(DbType.Guid)); - Assert.That(booleanColumn1.Type, Is.EqualTo(DbType.Boolean)); - Assert.That(int32Column1.Type, Is.EqualTo(DbType.Int32)); - Assert.That(int64column1.Type, Is.EqualTo(DbType.Int64)); - Assert.That(stringColumn1.Type, Is.EqualTo(DbType.String)); - Assert.That(stringColumn2.Type, Is.EqualTo(DbType.String)); - Assert.That(stringColumn2.Size, Is.EqualTo(30)); + // var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); + // var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); + // var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); + // var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); + // var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); + // var int64column1 = columns.Single(x => x.Name == int64ColumnName1); + // var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); + // var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); } } diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs index 97d7cfe3..68260f43 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -1,4 +1,3 @@ -using System; using System.Data; using System.Linq; using DotNetProjects.Migrator.Framework; @@ -8,24 +7,64 @@ namespace Migrator.Tests.Providers.PostgreSQL; [TestFixture] [Category("Postgre")] -public class PostgreSQLTransformationProvider_GetColumnsTypeTests : PostgreSQLTransformationProviderTestBase +public class PostgreSQLTransformationProvider_GetColumnTypeTests : PostgreSQLTransformationProviderTestBase { [Test] - public void AddTableWithPrimaryKeyIdentity_Succeeds() + public void GetColumns_DataTypeResolveSucceeds() { // Arrange const string testTableName = "MyDefaultTestTable"; - const string propertyName1 = "Color1"; - const string propertyName2 = "Color2"; + const string dateTimeColumnName1 = "datetimecolumn1"; + const string dateTimeColumnName2 = "datetimecolumn2"; + const string decimalColumnName1 = "decimalcolumn"; + const string guidColumnName1 = "guidcolumn1"; + const string booleanColumnName1 = "booleancolumn1"; + const string int32ColumnName1 = "int32column1"; + const string int64ColumnName1 = "int64column1"; + const string stringColumnName1 = "stringcolumn1"; + const string stringColumnName2 = "stringcolumn2"; + // Should be extended by remaining types Provider.AddTable(testTableName, - new Column(propertyName2, DbType.Decimal) + new Column(dateTimeColumnName1, DbType.DateTime), + new Column(dateTimeColumnName2, DbType.DateTime2), + new Column(decimalColumnName1, DbType.Decimal), + new Column(guidColumnName1, DbType.Guid), + new Column(booleanColumnName1, DbType.Boolean), + new Column(int32ColumnName1, DbType.Int32), + new Column(int64ColumnName1, DbType.Int64), + new Column(stringColumnName1, DbType.String), + new Column(stringColumnName2, DbType.String) { Size = 30 } ); // Act - var column = Provider.GetColumns(testTableName).Single(x => x.Name.Equals(propertyName1, StringComparison.OrdinalIgnoreCase)); - Provider.Insert(testTableName, [propertyName2], [3.448484]); + var columns = Provider.GetColumns(testTableName); - throw new NotImplementedException(); + var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); + var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); + var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); + var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); + var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); + var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); + var int64column1 = columns.Single(x => x.Name == int64ColumnName1); + var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); + var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); + + + // Assert + Assert.That(dateTimeColumn1.Type, Is.EqualTo(DbType.DateTime)); + Assert.That(dateTimeColumn1.Precision, Is.EqualTo(3)); + Assert.That(dateTimeColumn2.Type, Is.EqualTo(DbType.DateTime2)); + Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); + Assert.That(decimalColumn1.Type, Is.EqualTo(DbType.Decimal)); + Assert.That(decimalColumn1.Precision, Is.EqualTo(19)); + Assert.That(decimalColumn1.Scale, Is.EqualTo(5)); + Assert.That(guidColumn1.Type, Is.EqualTo(DbType.Guid)); + Assert.That(booleanColumn1.Type, Is.EqualTo(DbType.Boolean)); + Assert.That(int32Column1.Type, Is.EqualTo(DbType.Int32)); + Assert.That(int64column1.Type, Is.EqualTo(DbType.Int64)); + Assert.That(stringColumn1.Type, Is.EqualTo(DbType.String)); + Assert.That(stringColumn2.Type, Is.EqualTo(DbType.String)); + Assert.That(stringColumn2.Size, Is.EqualTo(30)); } } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 9874bc7c..bb35ce68 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -18,6 +18,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using Index = DotNetProjects.Migrator.Framework.Index; namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; @@ -384,12 +385,7 @@ public override Column[] GetColumns(string table) column.ColumnProperty |= isNullable ? ColumnProperty.Null : ColumnProperty.NotNull; - // if (defaultValueString != null && defaultValueString != DBNull.Value) - // { - // column.DefaultValue = defaultValueString; - // } - - if (column.DefaultValue != null) + if (defaultValueString != null) { if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) { @@ -409,16 +405,26 @@ public override Column[] GetColumns(string table) } else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) { - if (column.DefaultValue is string defVal) + if (defaultValueString.StartsWith("'")) { - var dt = defVal; - if (defVal.StartsWith("'")) + var regEx = new Regex("(?<=')[^']*(?=')"); + + var match = regEx.Match(defaultValueString); + if (!match.Success) { - dt = defVal.Substring(1, defVal.Length - 2); + throw new Exception("Postgre default value for date time: We expected single quotes around the date time string."); } - var d = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); - column.DefaultValue = d; + var timeString = match.Value; + + // We convert to UTC since we restrict 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(); } } else if (column.Type == DbType.Guid) From 746432ecfc25649f0055d832e39f511dc1cce4d0 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 18:02:31 +0200 Subject: [PATCH 11/17] Adjustments for Postgre Guid and decimal default values --- ...ionProvider_GetColumnsDefaultValueTests.cs | 26 ++++++++++++++----- src/Migrator/Providers/Dialect.cs | 10 ++++++- .../PostgreSQLTransformationProvider.cs | 24 +++++++++++------ 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index b9147172..f4e9e2d1 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -10,11 +10,17 @@ namespace Migrator.Tests.Providers.PostgreSQL; [Category("Postgre")] public class PostgreSQLTransformationProvider_GetColumnsDefaultTypeTests : PostgreSQLTransformationProviderTestBase { + private const decimal DecimalDefaultValue = 14.56565m; + [Test] public void GetColumns_DataTypeResolveSucceeds() { // Arrange + var dateTimeDefaultValue = new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc); + var guidDefaultValue = Guid.NewGuid(); + const string testTableName = "MyDefaultTestTable"; + const string dateTimeColumnName1 = "datetimecolumn1"; const string dateTimeColumnName2 = "datetimecolumn2"; const string decimalColumnName1 = "decimalcolumn"; @@ -27,10 +33,10 @@ public void GetColumns_DataTypeResolveSucceeds() // Should be extended by remaining types Provider.AddTable(testTableName, - new Column(dateTimeColumnName1, DbType.DateTime, new DateTime(2000, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)), - new Column(dateTimeColumnName2, DbType.DateTime2, new DateTime(2000, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)) - // new Column(decimalColumnName1, DbType.Decimal), - // new Column(guidColumnName1, DbType.Guid), + new Column(dateTimeColumnName1, DbType.DateTime, dateTimeDefaultValue), + new Column(dateTimeColumnName2, DbType.DateTime2, dateTimeDefaultValue), + new Column(decimalColumnName1, DbType.Decimal, DecimalDefaultValue), + new Column(guidColumnName1, DbType.Guid, guidDefaultValue) // new Column(booleanColumnName1, DbType.Boolean), // new Column(int32ColumnName1, DbType.Int32), // new Column(int64ColumnName1, DbType.Int64), @@ -41,14 +47,20 @@ public void GetColumns_DataTypeResolveSucceeds() // Act var columns = Provider.GetColumns(testTableName); + // Assert var dateTimeColumn1 = columns.Single(x => x.Name == dateTimeColumnName1); - // var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); - // var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); - // var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); + var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); + var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); + var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); // var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); // var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); // var int64column1 = columns.Single(x => x.Name == int64ColumnName1); // var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); // var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); + + Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); + Assert.That(decimalColumn1.DefaultValue, Is.EqualTo(DecimalDefaultValue)); + Assert.That(guidColumn1.DefaultValue, Is.EqualTo(guidDefaultValue)); } } diff --git a/src/Migrator/Providers/Dialect.cs b/src/Migrator/Providers/Dialect.cs index d66ae1fc..5ca470f3 100644 --- a/src/Migrator/Providers/Dialect.cs +++ b/src/Migrator/Providers/Dialect.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; using DotNetProjects.Migrator.Framework; namespace DotNetProjects.Migrator.Providers; @@ -344,7 +345,9 @@ public virtual string Default(object defaultValue) } else if (defaultValue is Guid) { - return string.Format("DEFAULT '{0}'", defaultValue.ToString()); + var guidValue = string.Format("DEFAULT '{0}'", defaultValue.ToString()); + + return guidValue; } else if (defaultValue is DateTime) { @@ -355,6 +358,11 @@ public virtual string Default(object defaultValue) defaultValue = ((string)defaultValue).Replace("'", "''"); defaultValue = "'" + defaultValue + "'"; } + else if (defaultValue is decimal) + { + // .ToString("N") does not exist in old .NET version + defaultValue = Convert.ToString(defaultValue, CultureInfo.InvariantCulture); + } return string.Format("DEFAULT {0}", defaultValue); } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index bb35ce68..f96842fc 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -28,6 +28,8 @@ namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; /// public class PostgreSQLTransformationProvider : TransformationProvider { + private Regex stripSingleQuoteRegEx = new("(?<=')[^']*(?=')"); + public PostgreSQLTransformationProvider(Dialect dialect, string connectionString, string defaultSchema, string scope, string providerName) : base(dialect, connectionString, defaultSchema, scope) { @@ -407,9 +409,8 @@ public override Column[] GetColumns(string table) { if (defaultValueString.StartsWith("'")) { - var regEx = new Regex("(?<=')[^']*(?=')"); + var match = stripSingleQuoteRegEx.Match(defaultValueString); - var match = regEx.Match(defaultValueString); if (!match.Success) { throw new Exception("Postgre default value for date time: We expected single quotes around the date time string."); @@ -429,18 +430,25 @@ public override Column[] GetColumns(string table) } else if (column.Type == DbType.Guid) { - if (column.DefaultValue is string defVal) + if (defaultValueString.StartsWith("'")) { - var dt = defVal; + var match = stripSingleQuoteRegEx.Match(defaultValueString); - if (defVal.StartsWith("'")) + if (!match.Success) { - dt = defVal.Substring(1, defVal.Length - 2); + throw new Exception("Postgre default value for uniqueidentifier: We expected single quotes around the Guid string."); } - var d = Guid.Parse(dt); - column.DefaultValue = d; + column.DefaultValue = Guid.Parse(match.Value); } + else + { + throw new NotImplementedException(); + } + } + else if (column.Type == DbType.Decimal) + { + column.DefaultValue = decimal.Parse(defaultValueString, CultureInfo.InvariantCulture); } } From 5944df7cbf8ba11957fccdd1ceadc8b0a2174237 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 18:26:59 +0200 Subject: [PATCH 12/17] Updated boolean default values in Postgre --- ...ionProvider_GetColumnsDefaultValueTests.cs | 28 +++++++++++-------- .../PostgreSQLTransformationProvider.cs | 16 ++++++++++- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index f4e9e2d1..ba4ffe7b 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -36,12 +36,15 @@ public void GetColumns_DataTypeResolveSucceeds() new Column(dateTimeColumnName1, DbType.DateTime, dateTimeDefaultValue), new Column(dateTimeColumnName2, DbType.DateTime2, dateTimeDefaultValue), new Column(decimalColumnName1, DbType.Decimal, DecimalDefaultValue), - new Column(guidColumnName1, DbType.Guid, guidDefaultValue) - // new Column(booleanColumnName1, DbType.Boolean), - // new Column(int32ColumnName1, DbType.Int32), - // new Column(int64ColumnName1, DbType.Int64), - // new Column(stringColumnName1, DbType.String), - // new Column(stringColumnName2, DbType.String) { Size = 30 } + new Column(guidColumnName1, DbType.Guid, guidDefaultValue), + + // other boolean default values are tested in another test + new Column(booleanColumnName1, DbType.Boolean, true), + + new Column(int32ColumnName1, DbType.Int32, 43), + new Column(int64ColumnName1, DbType.Int64, 88), + new Column(stringColumnName1, DbType.String), + new Column(stringColumnName2, DbType.String) { Size = 30 } ); // Act @@ -52,15 +55,18 @@ public void GetColumns_DataTypeResolveSucceeds() var dateTimeColumn2 = columns.Single(x => x.Name == dateTimeColumnName2); var decimalColumn1 = columns.Single(x => x.Name == decimalColumnName1); var guidColumn1 = columns.Single(x => x.Name == guidColumnName1); - // var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); - // var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); - // var int64column1 = columns.Single(x => x.Name == int64ColumnName1); - // var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); - // var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); + var booleanColumn1 = columns.Single(x => x.Name == booleanColumnName1); + var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); + var int64Column1 = columns.Single(x => x.Name == int64ColumnName1); + var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); + var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); Assert.That(decimalColumn1.DefaultValue, Is.EqualTo(DecimalDefaultValue)); Assert.That(guidColumn1.DefaultValue, Is.EqualTo(guidDefaultValue)); + Assert.That(booleanColumn1.DefaultValue, Is.True); + Assert.That(int32Column1.DefaultValue, Is.EqualTo(43)); + Assert.That(int64Column1.DefaultValue, Is.EqualTo(88)); } } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index f96842fc..47aef6e5 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -403,7 +403,21 @@ public override Column[] GetColumns(string table) } else if (column.Type == DbType.Boolean) { - column.DefaultValue = column.DefaultValue.ToString().Trim() == "1" || column.DefaultValue.ToString().Trim().Equals("TRUE", StringComparison.OrdinalIgnoreCase) || column.DefaultValue.ToString().Trim() == "YES"; + var truthy = new[] { "1", "TRUE", "YES", "'true'", "on", "'on'", "t", "'t'" }; + var falsy = new[] { "0", "FALSE", "NO", "'false'", "off", "'off'", "f", "'f'" }; + + if (truthy.Any(x => x.Equals(defaultValueString.Trim(), StringComparison.OrdinalIgnoreCase))) + { + column.DefaultValue = true; + } + else if (falsy.Any(x => x.Equals(defaultValueString.Trim(), StringComparison.OrdinalIgnoreCase))) + { + column.DefaultValue = false; + } + else + { + throw new NotImplementedException($"Cannot interpret the given default value in column '{column.Name}'"); + } } else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) { From 2f98da033b8f82690cdbc597b835111b75d94baa Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Fri, 8 Aug 2025 19:00:35 +0200 Subject: [PATCH 13/17] Added string to Postgre default value --- ...ionProvider_GetColumnsDefaultValueTests.cs | 10 +++---- .../PostgreSQLTransformationProvider.cs | 28 +++++++++++++++++-- .../Providers/TransformationProvider.cs | 8 ++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index ba4ffe7b..ac9cbf31 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -29,7 +29,6 @@ public void GetColumns_DataTypeResolveSucceeds() const string int32ColumnName1 = "int32column1"; const string int64ColumnName1 = "int64column1"; const string stringColumnName1 = "stringcolumn1"; - const string stringColumnName2 = "stringcolumn2"; // Should be extended by remaining types Provider.AddTable(testTableName, @@ -41,10 +40,9 @@ public void GetColumns_DataTypeResolveSucceeds() // other boolean default values are tested in another test new Column(booleanColumnName1, DbType.Boolean, true), - new Column(int32ColumnName1, DbType.Int32, 43), - new Column(int64ColumnName1, DbType.Int64, 88), - new Column(stringColumnName1, DbType.String), - new Column(stringColumnName2, DbType.String) { Size = 30 } + new Column(int32ColumnName1, DbType.Int32, defaultValue: 43), + new Column(int64ColumnName1, DbType.Int64, defaultValue: 88), + new Column(stringColumnName1, DbType.String, defaultValue: "Hello") ); // Act @@ -59,7 +57,6 @@ public void GetColumns_DataTypeResolveSucceeds() var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); var int64Column1 = columns.Single(x => x.Name == int64ColumnName1); var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); - var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); @@ -68,5 +65,6 @@ public void GetColumns_DataTypeResolveSucceeds() Assert.That(booleanColumn1.DefaultValue, Is.True); Assert.That(int32Column1.DefaultValue, Is.EqualTo(43)); Assert.That(int64Column1.DefaultValue, Is.EqualTo(88)); + Assert.That(stringColumn1.DefaultValue, Is.EqualTo("Hello")); } } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 47aef6e5..7577aa6e 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -391,15 +391,15 @@ public override Column[] GetColumns(string table) { if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) { - column.DefaultValue = long.Parse(column.DefaultValue.ToString()); + column.DefaultValue = long.Parse(defaultValueString.ToString()); } else if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) { - column.DefaultValue = ulong.Parse(column.DefaultValue.ToString()); + column.DefaultValue = ulong.Parse(defaultValueString.ToString()); } else if (column.Type == DbType.Double || column.Type == DbType.Single) { - column.DefaultValue = double.Parse(column.DefaultValue.ToString()); + column.DefaultValue = double.Parse(defaultValueString.ToString()); } else if (column.Type == DbType.Boolean) { @@ -464,6 +464,28 @@ public override Column[] GetColumns(string table) { column.DefaultValue = decimal.Parse(defaultValueString, CultureInfo.InvariantCulture); } + else if (column.Type == DbType.String) + { + if (defaultValueString.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(defaultValueString); + + if (!match.Success) + { + throw new Exception("Postgre default value for date time: We expected single quotes around the date time string."); + } + + column.DefaultValue = match.Value; + } + else + { + throw new NotImplementedException(); + } + } + else + { + throw new NotImplementedException(); + } } columns.Add(column); diff --git a/src/Migrator/Providers/TransformationProvider.cs b/src/Migrator/Providers/TransformationProvider.cs index 023e76c5..8b454831 100644 --- a/src/Migrator/Providers/TransformationProvider.cs +++ b/src/Migrator/Providers/TransformationProvider.cs @@ -1784,6 +1784,14 @@ public virtual List GetPrimaryKeys(IEnumerable columns) public virtual void AddColumnDefaultValue(string table, string column, object defaultValue) { + if (defaultValue is DateTime defaultValueDateTime) + { + if (defaultValueDateTime.Kind != DateTimeKind.Utc) + { + throw new Exception("We only accept UTC values as default DateTime values."); + } + } + table = QuoteTableNameIfRequired(table); column = this.QuoteColumnNameIfRequired(column); var def = Dialect.Default(defaultValue); From 355ff7e3f41a3e75dbe1a4e4622336722ada7516 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Mon, 11 Aug 2025 08:33:21 +0200 Subject: [PATCH 14/17] Minor change --- .../Impl/PostgreSQL/PostgreSQLTransformationProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 7577aa6e..ff151f71 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -427,12 +427,12 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for date time: We expected single quotes around the date time string."); + throw new Exception("Postgre default value for date time: We expect single quotes around the date time string."); } var timeString = match.Value; - // We convert to UTC since we restrict to UTC on default value definition. + // 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; From adb0023392119b1dcef4344a8c0cddaec1f23790 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Mon, 11 Aug 2025 09:48:00 +0200 Subject: [PATCH 15/17] Added Postgre type bytea for DbType.Binary --- ...eSQLTransformationProvider_GetColumnsTypeTests.cs | 1 + .../PostgreSQL/PostgreSQLTransformationProvider.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs index 68260f43..55c77ade 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -23,6 +23,7 @@ public void GetColumns_DataTypeResolveSucceeds() const string int64ColumnName1 = "int64column1"; const string stringColumnName1 = "stringcolumn1"; const string stringColumnName2 = "stringcolumn2"; + const string byteColumnName = "bytecolumn"; // Should be extended by remaining types Provider.AddTable(testTableName, diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index ff151f71..4b01d3a9 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -368,9 +368,13 @@ public override Column[] GetColumns(string table) dbType = DbType.String; size = characterMaximumLength; } + else if (dataTypeString == "bytea") + { + dbType = DbType.Binary; + } else if (dataTypeString == "character" || dataTypeString.StartsWith("character(")) { - throw new NotSupportedException("Data type 'character' detected. We do not support 'character'. Use 'text' or 'character varying' instead"); + throw new NotSupportedException("Data type 'character' detected. 'character' is not supported. Use 'text' or 'character varying' instead."); } else { @@ -427,7 +431,7 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for date time: We expect single quotes around the date time string."); + throw new Exception("Postgre default value for date time: Single quotes around the date time string are expected."); } var timeString = match.Value; @@ -450,7 +454,7 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for uniqueidentifier: We expected single quotes around the Guid string."); + throw new Exception("Postgre default value for uniqueidentifier: Single quotes around the Guid string are expected."); } column.DefaultValue = Guid.Parse(match.Value); @@ -472,7 +476,7 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for date time: We expected single quotes around the date time string."); + throw new Exception("Postgre default value for date time: Single quotes around the date time string are expected."); } column.DefaultValue = match.Value; From 12561fc68a7b4a11e4765cee9c8569de6ecfca72 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Mon, 11 Aug 2025 10:20:15 +0200 Subject: [PATCH 16/17] Added default value binary. Conversion from and to Postgre notation --- ...ionProvider_GetColumnsDefaultValueTests.cs | 6 +++- ...nsformationProvider_GetColumnsTypeTests.cs | 7 +++-- src/Migrator/Providers/Dialect.cs | 5 +++ .../PostgreSQLTransformationProvider.cs | 31 +++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index ac9cbf31..3bbc9237 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -29,6 +29,7 @@ public void GetColumns_DataTypeResolveSucceeds() const string int32ColumnName1 = "int32column1"; const string int64ColumnName1 = "int64column1"; const string stringColumnName1 = "stringcolumn1"; + const string binaryColumnName1 = "binarycolumn1"; // Should be extended by remaining types Provider.AddTable(testTableName, @@ -42,7 +43,8 @@ public void GetColumns_DataTypeResolveSucceeds() new Column(int32ColumnName1, DbType.Int32, defaultValue: 43), new Column(int64ColumnName1, DbType.Int64, defaultValue: 88), - new Column(stringColumnName1, DbType.String, defaultValue: "Hello") + new Column(stringColumnName1, DbType.String, defaultValue: "Hello"), + new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }) ); // Act @@ -57,6 +59,7 @@ public void GetColumns_DataTypeResolveSucceeds() var int32Column1 = columns.Single(x => x.Name == int32ColumnName1); var int64Column1 = columns.Single(x => x.Name == int64ColumnName1); var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); + var binarycolumn1 = columns.Single(x => x.Name == binaryColumnName1); Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); @@ -66,5 +69,6 @@ public void GetColumns_DataTypeResolveSucceeds() Assert.That(int32Column1.DefaultValue, Is.EqualTo(43)); Assert.That(int64Column1.DefaultValue, Is.EqualTo(88)); Assert.That(stringColumn1.DefaultValue, Is.EqualTo("Hello")); + Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); } } diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs index 55c77ade..8c169b08 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -23,7 +23,7 @@ public void GetColumns_DataTypeResolveSucceeds() const string int64ColumnName1 = "int64column1"; const string stringColumnName1 = "stringcolumn1"; const string stringColumnName2 = "stringcolumn2"; - const string byteColumnName = "bytecolumn"; + const string binaryColumnName1 = "binarycolumn"; // Should be extended by remaining types Provider.AddTable(testTableName, @@ -35,7 +35,8 @@ public void GetColumns_DataTypeResolveSucceeds() new Column(int32ColumnName1, DbType.Int32), new Column(int64ColumnName1, DbType.Int64), new Column(stringColumnName1, DbType.String), - new Column(stringColumnName2, DbType.String) { Size = 30 } + new Column(stringColumnName2, DbType.String) { Size = 30 }, + new Column(binaryColumnName1, DbType.Binary) ); // Act @@ -50,6 +51,7 @@ public void GetColumns_DataTypeResolveSucceeds() var int64column1 = columns.Single(x => x.Name == int64ColumnName1); var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); + var binaryColumn1 = columns.Single(x => x.Name == binaryColumnName1); // Assert @@ -67,5 +69,6 @@ public void GetColumns_DataTypeResolveSucceeds() Assert.That(stringColumn1.Type, Is.EqualTo(DbType.String)); Assert.That(stringColumn2.Type, Is.EqualTo(DbType.String)); Assert.That(stringColumn2.Size, Is.EqualTo(30)); + Assert.That(binaryColumn1.Type, Is.EqualTo(DbType.Binary)); } } diff --git a/src/Migrator/Providers/Dialect.cs b/src/Migrator/Providers/Dialect.cs index 5ca470f3..7a8d9e49 100644 --- a/src/Migrator/Providers/Dialect.cs +++ b/src/Migrator/Providers/Dialect.cs @@ -363,6 +363,11 @@ public virtual string Default(object defaultValue) // .ToString("N") does not exist in old .NET version defaultValue = Convert.ToString(defaultValue, CultureInfo.InvariantCulture); } + else if (defaultValue is byte[] byteArray) + { + var convertedString = BitConverter.ToString(byteArray).Replace("-", "").ToLower(); + defaultValue = $"'\\x{convertedString}'"; + } return string.Format("DEFAULT {0}", defaultValue); } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index 4b01d3a9..9cc1a855 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs @@ -486,6 +486,37 @@ public override Column[] GetColumns(string table) throw new NotImplementedException(); } } + else if (column.Type == DbType.Binary) + { + if (defaultValueString.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(defaultValueString); + + if (!match.Success) + { + throw new Exception("Postgre default value for bytea: Single quotes around the bytea string are expected."); + } + + 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(); + } + } else { throw new NotImplementedException(); From e24fe5d151fe642d494a968ddd9c048b29664ba9 Mon Sep 17 00:00:00 2001 From: JaBistDuNarrisch Date: Mon, 11 Aug 2025 10:21:59 +0200 Subject: [PATCH 17/17] Minor change --- src/Migrator/Providers/TransformationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migrator/Providers/TransformationProvider.cs b/src/Migrator/Providers/TransformationProvider.cs index 8b454831..f7f6b9e6 100644 --- a/src/Migrator/Providers/TransformationProvider.cs +++ b/src/Migrator/Providers/TransformationProvider.cs @@ -1788,7 +1788,7 @@ public virtual void AddColumnDefaultValue(string table, string column, object de { if (defaultValueDateTime.Kind != DateTimeKind.Utc) { - throw new Exception("We only accept UTC values as default DateTime values."); + throw new Exception("Only UTC values are accepted as default DateTime values."); } }