diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs index 826001ec..0e9447b6 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsDefaultValueTests.cs @@ -32,6 +32,8 @@ public void GetColumns_DefaultValues_Succeeds() const string stringColumnName1 = "stringcolumn1"; const string binaryColumnName1 = "binarycolumn1"; const string doubleColumnName1 = "doublecolumn1"; + const string intervalColumnName1 = "intervalcolumn1"; + const string intervalColumnName2 = "intervalcolumn2"; // Should be extended by remaining types Provider.AddTable(testTableName, @@ -48,7 +50,9 @@ public void GetColumns_DefaultValues_Succeeds() new Column(int64ColumnName2, DbType.Int64, defaultValue: 0), new Column(stringColumnName1, DbType.String, defaultValue: "Hello"), new Column(binaryColumnName1, DbType.Binary, defaultValue: new byte[] { 12, 32, 34 }), - new Column(doubleColumnName1, DbType.Double, defaultValue: 84.874596565) + new Column(doubleColumnName1, DbType.Double, defaultValue: 84.874596565), + new Column(intervalColumnName1, MigratorDbType.Interval, defaultValue: new TimeSpan(100000, 3, 4, 5, 666)), + new Column(intervalColumnName2, MigratorDbType.Interval, defaultValue: new TimeSpan(0, 0, 0, 0, 666)) ); // Act @@ -66,6 +70,8 @@ public void GetColumns_DefaultValues_Succeeds() var stringColumn1 = columns.Single(x => x.Name == stringColumnName1); var binarycolumn1 = columns.Single(x => x.Name == binaryColumnName1); var doubleColumn1 = columns.Single(x => x.Name == doubleColumnName1); + var intervalColumn1 = columns.Single(x => x.Name == intervalColumnName1); + var intervalColumn2 = columns.Single(x => x.Name == intervalColumnName2); Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue)); @@ -77,6 +83,8 @@ public void GetColumns_DefaultValues_Succeeds() Assert.That(stringColumn1.DefaultValue, Is.EqualTo("Hello")); Assert.That(binarycolumn1.DefaultValue, Is.EqualTo(new byte[] { 12, 32, 34 })); Assert.That(doubleColumn1.DefaultValue, Is.EqualTo(84.874596565)); + Assert.That(intervalColumn1.DefaultValue, Is.EqualTo(new TimeSpan(100000, 3, 4, 5, 666))); + Assert.That(intervalColumn2.DefaultValue, Is.EqualTo(new TimeSpan(0, 0, 0, 0, 666))); } // 1 will coerce to true on inserts but not for default values in Postgre SQL - same for 0 to false @@ -104,9 +112,6 @@ public void GetColumns_DefaultValues_Succeeds() public void GetColumns_DefaultValueBooleanValues_Succeeds(object inboundBooleanDefaultValue, bool outboundBooleanDefaultValue) { // Arrange - var dateTimeDefaultValue = new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc); - var guidDefaultValue = Guid.NewGuid(); - const string testTableName = "MyDefaultTestTable"; const string booleanColumnName1 = "booleancolumn1"; diff --git a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs index aeaff598..de7cf454 100644 --- a/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs +++ b/src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumnsTypeTests.cs @@ -25,6 +25,7 @@ public void GetColumns_DataTypeResolveSucceeds() const string stringColumnName2 = "stringcolumn2"; const string binaryColumnName1 = "binarycolumn"; const string doubleColumnName1 = "doublecolumn"; + const string intervalColumnName1 = "intervalcolumn"; // Should be extended by remaining types Provider.AddTable(testTableName, @@ -38,9 +39,11 @@ public void GetColumns_DataTypeResolveSucceeds() new Column(stringColumnName1, DbType.String), new Column(stringColumnName2, DbType.String) { Size = 30 }, new Column(binaryColumnName1, DbType.Binary), - new Column(doubleColumnName1, DbType.Double) + new Column(doubleColumnName1, DbType.Double), + new Column(intervalColumnName1, MigratorDbType.Interval) ); + // Act var columns = Provider.GetColumns(testTableName); @@ -55,24 +58,26 @@ public void GetColumns_DataTypeResolveSucceeds() var stringColumn2 = columns.Single(x => x.Name == stringColumnName2); var binaryColumn1 = columns.Single(x => x.Name == binaryColumnName1); var doubleColumn1 = columns.Single(x => x.Name == doubleColumnName1); + var intervalColumn1 = columns.Single(x => x.Name == intervalColumnName1); // Assert - Assert.That(dateTimeColumn1.Type, Is.EqualTo(DbType.DateTime)); + Assert.That(dateTimeColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime)); Assert.That(dateTimeColumn1.Precision, Is.EqualTo(3)); - Assert.That(dateTimeColumn2.Type, Is.EqualTo(DbType.DateTime2)); + Assert.That(dateTimeColumn2.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime2)); Assert.That(dateTimeColumn2.Precision, Is.EqualTo(6)); - Assert.That(decimalColumn1.Type, Is.EqualTo(DbType.Decimal)); + Assert.That(decimalColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.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(guidColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Guid)); + Assert.That(booleanColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Boolean)); + Assert.That(int32Column1.MigratorDbType, Is.EqualTo(MigratorDbType.Int32)); + Assert.That(int64column1.MigratorDbType, Is.EqualTo(MigratorDbType.Int64)); + Assert.That(stringColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.String)); + Assert.That(stringColumn2.MigratorDbType, Is.EqualTo(MigratorDbType.String)); Assert.That(stringColumn2.Size, Is.EqualTo(30)); - Assert.That(binaryColumn1.Type, Is.EqualTo(DbType.Binary)); - Assert.That(doubleColumn1.Type, Is.EqualTo(DbType.Double)); + Assert.That(binaryColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Binary)); + Assert.That(doubleColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Double)); + Assert.That(intervalColumn1.MigratorDbType, Is.EqualTo(MigratorDbType.Interval)); } } diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs index 07d00246..f536745e 100644 --- a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs +++ b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs @@ -1,4 +1,5 @@ using DotNetProjects.Migrator.Framework; +using System; using System.Data; namespace DotNetProjects.Migrator.Providers.Impl.PostgreSQL; @@ -112,6 +113,18 @@ public override ITransformationProvider GetTransformationProvider(Dialect dialec return new PostgreSQLTransformationProvider(dialect, connection, defaultSchema, scope, providerName); } + public override string Default(object defaultValue) + { + if (defaultValue is TimeSpan timeSpan) + { + var intervalPostgreNotation = $"{(int)timeSpan.TotalHours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}.{timeSpan.Milliseconds:D3}"; + + return $"DEFAULT '{intervalPostgreNotation}'"; + } + + return base.Default(defaultValue); + } + //public override string SqlForProperty(ColumnProperty property, Column column) //{ // if (property == ColumnProperty.Identity && (column.Type == DbType.Int64 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64)) diff --git a/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs b/src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs index b6698201..2729605d 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.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using Index = DotNetProjects.Migrator.Framework.Index; @@ -181,11 +180,11 @@ public override void ChangeColumn(string table, Column column) var change1 = string.Format("{0} TYPE {1}", QuoteColumnNameIfRequired(mapper.Name), mapper.type); - if ((oldColumn.Type == DbType.Int16 || oldColumn.Type == DbType.Int32 || oldColumn.Type == DbType.Int64 || oldColumn.Type == DbType.Decimal) && column.Type == DbType.Boolean) + if ((oldColumn.MigratorDbType == MigratorDbType.Int16 || oldColumn.MigratorDbType == MigratorDbType.Int32 || oldColumn.MigratorDbType == MigratorDbType.Int64 || oldColumn.MigratorDbType == MigratorDbType.Decimal) && column.MigratorDbType == MigratorDbType.Boolean) { change1 += string.Format(" USING CASE {0} WHEN 1 THEN true ELSE false END", QuoteColumnNameIfRequired(mapper.Name)); } - else if (column.Type == DbType.Boolean) + else if (column.MigratorDbType == MigratorDbType.Boolean) { change1 += string.Format(" USING CASE {0} WHEN '1' THEN true ELSE false END", QuoteColumnNameIfRequired(mapper.Name)); } @@ -286,19 +285,19 @@ public override Column[] GetColumns(string table) var numericPrecision = reader.IsDBNull(numericPrecisionOrdinal) ? null : (int?)reader.GetInt32(numericPrecisionOrdinal); var numericScale = reader.IsDBNull(numericScaleOrdinal) ? null : (int?)reader.GetInt32(numericScaleOrdinal); - DbType dbType = 0; + MigratorDbType dbType = 0; int? precision = null; int? scale = null; int? size = null; if (new[] { "timestamptz", "timestamp with time zone" }.Contains(dataTypeString)) { - dbType = DbType.DateTimeOffset; + dbType = MigratorDbType.DateTimeOffset; precision = dateTimePrecision; } else if (dataTypeString == "double precision") { - dbType = DbType.Double; + dbType = MigratorDbType.Double; scale = numericScale; precision = numericPrecision; } @@ -307,77 +306,77 @@ public override Column[] GetColumns(string table) // 6 is the maximum in PostgreSQL if (dateTimePrecision > 5) { - dbType = DbType.DateTime2; + dbType = MigratorDbType.DateTime2; } else { - dbType = DbType.DateTime; + dbType = MigratorDbType.DateTime; } precision = dateTimePrecision; } else if (dataTypeString == "smallint") { - dbType = DbType.Int16; + dbType = MigratorDbType.Int16; } else if (dataTypeString == "integer") { - dbType = DbType.Int32; + dbType = MigratorDbType.Int32; } else if (dataTypeString == "bigint") { - dbType = DbType.Int64; + dbType = MigratorDbType.Int64; } else if (dataTypeString == "numeric") { - dbType = DbType.Decimal; + dbType = MigratorDbType.Decimal; precision = numericPrecision; scale = numericScale; } else if (dataTypeString == "real") { - dbType = DbType.Single; + dbType = MigratorDbType.Single; + } + else if (dataTypeString == "interval") + { + dbType = MigratorDbType.Interval; } else if (dataTypeString == "money") { - dbType = DbType.Currency; + dbType = MigratorDbType.Currency; } else if (dataTypeString == "date") { - dbType = DbType.Date; + dbType = MigratorDbType.Date; } else if (dataTypeString == "byte") { - dbType = DbType.Binary; + dbType = MigratorDbType.Binary; } else if (dataTypeString == "uuid") { - dbType = DbType.Guid; + dbType = MigratorDbType.Guid; } else if (dataTypeString == "xml") { - dbType = DbType.Xml; + dbType = MigratorDbType.Xml; } else if (dataTypeString == "time") { - dbType = DbType.Time; - } - else if (dataTypeString == "interval") - { - throw new NotImplementedException(); + dbType = MigratorDbType.Time; } else if (dataTypeString == "boolean") { - dbType = DbType.Boolean; + dbType = MigratorDbType.Boolean; } else if (dataTypeString == "text" || dataTypeString == "character varying") { - dbType = DbType.String; + dbType = MigratorDbType.String; size = characterMaximumLength; } else if (dataTypeString == "bytea") { - dbType = DbType.Binary; + dbType = MigratorDbType.Binary; } else if (dataTypeString == "character" || dataTypeString.StartsWith("character(")) { @@ -400,19 +399,52 @@ public override Column[] GetColumns(string table) if (defaultValueString != null) { - if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64) + if (column.MigratorDbType == MigratorDbType.Int16 || column.MigratorDbType == MigratorDbType.Int32 || column.MigratorDbType == MigratorDbType.Int64) { column.DefaultValue = long.Parse(defaultValueString.ToString()); } - else if (column.Type == DbType.UInt16 || column.Type == DbType.UInt32 || column.Type == DbType.UInt64) + else if (column.MigratorDbType == MigratorDbType.UInt16 || column.MigratorDbType == MigratorDbType.UInt32 || column.MigratorDbType == MigratorDbType.UInt64) { column.DefaultValue = ulong.Parse(defaultValueString.ToString()); } - else if (column.Type == DbType.Double || column.Type == DbType.Single) + else if (column.MigratorDbType == MigratorDbType.Double || column.MigratorDbType == MigratorDbType.Single) { column.DefaultValue = double.Parse(defaultValueString.ToString(), CultureInfo.InvariantCulture); } - else if (column.Type == DbType.Boolean) + else if (column.MigratorDbType == MigratorDbType.Interval) + { + if (defaultValueString.StartsWith("'")) + { + var match = stripSingleQuoteRegEx.Match(defaultValueString); + + if (!match.Success) + { + throw new Exception("Postgre default value for interval: Single quotes around the interval string are expected."); + } + + column.DefaultValue = match.Value; + var splitted = match.Value.Split(':'); + if (splitted.Length != 3) + { + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}' unexpected pattern."); + } + + var hours = int.Parse(splitted[0], CultureInfo.InvariantCulture); + var minutes = int.Parse(splitted[1], CultureInfo.InvariantCulture); + var splitted2 = splitted[2].Split('.'); + var seconds = int.Parse(splitted2[0], CultureInfo.InvariantCulture); + var milliseconds = int.Parse(splitted2[1], CultureInfo.InvariantCulture); + + column.DefaultValue = new TimeSpan(0, hours, minutes, seconds, milliseconds); + } + 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 interpret {defaultValueString} 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'" }; @@ -427,10 +459,10 @@ public override Column[] GetColumns(string table) } else { - throw new NotImplementedException($"Cannot interpret the given default value in column '{column.Name}'"); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } } - else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2) + else if (column.MigratorDbType == MigratorDbType.DateTime || column.MigratorDbType == MigratorDbType.DateTime2) { if (defaultValueString.StartsWith("'")) { @@ -438,7 +470,7 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for date time: Single quotes around the date time string are expected."); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } var timeString = match.Value; @@ -450,10 +482,10 @@ public override Column[] GetColumns(string table) } else { - throw new NotImplementedException(); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } } - else if (column.Type == DbType.Guid) + else if (column.MigratorDbType == MigratorDbType.Guid) { if (defaultValueString.StartsWith("'")) { @@ -461,21 +493,21 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for uniqueidentifier: Single quotes around the Guid string are expected."); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } column.DefaultValue = Guid.Parse(match.Value); } else { - throw new NotImplementedException(); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } } - else if (column.Type == DbType.Decimal) + else if (column.MigratorDbType == MigratorDbType.Decimal) { column.DefaultValue = decimal.Parse(defaultValueString, CultureInfo.InvariantCulture); } - else if (column.Type == DbType.String) + else if (column.MigratorDbType == MigratorDbType.String) { if (defaultValueString.StartsWith("'")) { @@ -493,7 +525,7 @@ public override Column[] GetColumns(string table) throw new NotImplementedException(); } } - else if (column.Type == DbType.Binary) + else if (column.MigratorDbType == MigratorDbType.Binary) { if (defaultValueString.StartsWith("'")) { @@ -501,7 +533,7 @@ public override Column[] GetColumns(string table) if (!match.Success) { - throw new Exception("Postgre default value for bytea: Single quotes around the bytea string are expected."); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } var singleQuoteString = match.Value; @@ -521,7 +553,7 @@ public override Column[] GetColumns(string table) } else { - throw new NotImplementedException(); + throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'"); } } else