Skip to content

Commit a584a2c

Browse files
JaBistDuNarrischJaBistDuNarrisch
authored andcommitted
Read data type in GetColumns in SQL Server
1 parent ce77eae commit a584a2c

File tree

3 files changed

+105
-31
lines changed

3 files changed

+105
-31
lines changed

src/Migrator.Tests/Providers/Generic/TransformationProvider_GetColumns_GenericTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ namespace Migrator.Tests.Providers.Generic;
1212
/// </summary>
1313
public abstract class TransformationProvider_GetColumns_GenericTests : TransformationProviderBase
1414
{
15-
16-
1715
[Test]
1816
public void GetColumns_DefaultValues_Succeeds()
1917
{

src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ public override Column[] GetColumns(string table)
470470
{
471471
// We assume that the value was added using this migrator so we do not interpret things like '2 days 01:02:03' if you
472472
// added such format you will run into this exception.
473-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}' unexpected pattern.");
473+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}' unexpected pattern.");
474474
}
475475
}
476476
else if (column.MigratorDbType == MigratorDbType.Boolean)
@@ -488,7 +488,7 @@ public override Column[] GetColumns(string table)
488488
}
489489
else
490490
{
491-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
491+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}'");
492492
}
493493
}
494494
else if (column.MigratorDbType == MigratorDbType.DateTime || column.MigratorDbType == MigratorDbType.DateTime2)
@@ -499,7 +499,7 @@ public override Column[] GetColumns(string table)
499499

500500
if (!match.Success)
501501
{
502-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
502+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}'");
503503
}
504504

505505
var timeString = match.Value;
@@ -511,7 +511,7 @@ public override Column[] GetColumns(string table)
511511
}
512512
else
513513
{
514-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
514+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}'");
515515
}
516516
}
517517
else if (column.MigratorDbType == MigratorDbType.Guid)
@@ -522,14 +522,14 @@ public override Column[] GetColumns(string table)
522522

523523
if (!match.Success)
524524
{
525-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
525+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}'");
526526
}
527527

528528
column.DefaultValue = Guid.Parse(match.Value);
529529
}
530530
else
531531
{
532-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
532+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}'");
533533
}
534534
}
535535
else if (column.MigratorDbType == MigratorDbType.Decimal)
@@ -562,7 +562,7 @@ public override Column[] GetColumns(string table)
562562

563563
if (!match.Success)
564564
{
565-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
565+
throw new NotImplementedException($"Cannot pars {defaultValueString} in column '{column.Name}'");
566566
}
567567

568568
var singleQuoteString = match.Value;
@@ -582,7 +582,7 @@ public override Column[] GetColumns(string table)
582582
}
583583
else
584584
{
585-
throw new NotImplementedException($"Cannot interpret {defaultValueString} in column '{column.Name}'");
585+
throw new NotImplementedException($"Cannot parse {defaultValueString} in column '{column.Name}'");
586586
}
587587
}
588588
else

src/Migrator/Providers/Impl/SqlServer/SqlServerTransformationProvider.cs

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
using System.Collections.Generic;
1717
using System.Data;
1818
using System.Globalization;
19+
using System.Linq;
20+
using System.Text.RegularExpressions;
1921
using Index = DotNetProjects.Migrator.Framework.Index;
2022

2123
namespace DotNetProjects.Migrator.Providers.Impl.SqlServer;
@@ -339,12 +341,19 @@ public override Column[] GetColumns(string table)
339341
using (
340342
var reader =
341343
ExecuteQuery(cmd,
342-
string.Format("select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION), COLUMN_DEFAULT, NUMERIC_SCALE from INFORMATION_SCHEMA.COLUMNS where table_name = '{0}'", table)))
344+
string.Format("SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH , NUMERIC_PRECISION), COLUMN_DEFAULT, NUMERIC_SCALE, CHARACTER_MAXIMUM_LENGTH from INFORMATION_SCHEMA.COLUMNS where table_name = '{0}'", table)))
343345
{
344346
while (reader.Read())
345347
{
346348
var column = new Column(reader.GetString(0), DbType.String);
347349

350+
var defaultValueOrdinal = reader.GetOrdinal("COLUMN_DEFAULT");
351+
var dataTypeOrdinal = reader.GetOrdinal("DATA_TYPE");
352+
var characterMaximumLengthOrdinal = reader.GetOrdinal("CHARACTER_MAXIMUM_LENGTH");
353+
354+
var defaultValueString = reader.IsDBNull(defaultValueOrdinal) ? null : reader.GetString(defaultValueOrdinal).Trim();
355+
var characterMaximumLength = reader.IsDBNull(characterMaximumLengthOrdinal) ? (int?)null : reader.GetInt32(characterMaximumLengthOrdinal);
356+
348357
if (pkColumns.Contains(column.Name))
349358
{
350359
column.ColumnProperty |= ColumnProperty.PrimaryKey;
@@ -358,30 +367,78 @@ public override Column[] GetColumns(string table)
358367
var nullableStr = reader.GetString(1);
359368
var isNullable = nullableStr == "YES";
360369

361-
if (!reader.IsDBNull(2))
370+
var dataTypeString = reader.GetString(dataTypeOrdinal);
371+
372+
if (dataTypeString == "date")
373+
{
374+
column.MigratorDbType = MigratorDbType.Date;
375+
}
376+
else if (dataTypeString == "int")
377+
{
378+
column.MigratorDbType = MigratorDbType.Int32;
379+
}
380+
else if (dataTypeString == "smallint")
381+
{
382+
column.MigratorDbType = MigratorDbType.Int16;
383+
}
384+
else if (dataTypeString == "tinyint")
385+
{
386+
column.MigratorDbType = MigratorDbType.Byte;
387+
}
388+
else if (dataTypeString == "bit")
389+
{
390+
column.MigratorDbType = MigratorDbType.Boolean;
391+
}
392+
else if (dataTypeString == "money")
393+
{
394+
column.MigratorDbType = MigratorDbType.Currency;
395+
}
396+
else if (dataTypeString == "float")
397+
{
398+
column.MigratorDbType = MigratorDbType.Double;
399+
}
400+
else if (new[] { "text", "nchar", "ntext", "varchar", "nvarchar" }.Contains(dataTypeString))
401+
{
402+
// We use string for all string-like data types.
403+
column.MigratorDbType = MigratorDbType.String;
404+
column.Size = characterMaximumLength.Value;
405+
}
406+
else if (dataTypeString == "decimal")
407+
{
408+
column.MigratorDbType = MigratorDbType.Decimal;
409+
}
410+
else if (dataTypeString == "datetime")
362411
{
363-
var type = reader.GetString(2);
364-
column.Type = Dialect.GetDbTypeFromString(type);
412+
column.MigratorDbType = MigratorDbType.DateTime;
413+
}
414+
else if (dataTypeString == "datetime2")
415+
{
416+
column.MigratorDbType = MigratorDbType.DateTime2;
417+
}
418+
else if (dataTypeString == "datetimeoffset")
419+
{
420+
column.MigratorDbType = MigratorDbType.DateTimeOffset;
421+
}
422+
else if (dataTypeString == "binary" || dataTypeString == "varbinary")
423+
{
424+
column.MigratorDbType = MigratorDbType.Binary;
425+
}
426+
else if (dataTypeString == "uniqueidentifier")
427+
{
428+
column.MigratorDbType = MigratorDbType.Guid;
429+
}
430+
else
431+
{
432+
throw new NotImplementedException($"The data type '{dataTypeString}' is not implemented yet. Please file an issue.");
365433
}
366434

367435
if (!reader.IsDBNull(3))
368436
{
369437
column.Size = reader.GetInt32(3);
370438
}
371439

372-
if (!reader.IsDBNull(4))
440+
if (defaultValueString != null)
373441
{
374-
column.DefaultValue = reader.GetValue(4);
375-
376-
if (column.DefaultValue.ToString()[1] == '(' || column.DefaultValue.ToString()[1] == '\'')
377-
{
378-
column.DefaultValue = column.DefaultValue.ToString().Substring(2, column.DefaultValue.ToString().Length - 4); // Example "((10))" or "('false')"
379-
}
380-
else
381-
{
382-
column.DefaultValue = column.DefaultValue.ToString().Substring(1, column.DefaultValue.ToString().Length - 2); // Example "(CONVERT([datetime],'20000101',(112)))"
383-
}
384-
385442
if (column.Type == DbType.Int16 || column.Type == DbType.Int32 || column.Type == DbType.Int64)
386443
{
387444
column.DefaultValue = long.Parse(column.DefaultValue.ToString());
@@ -400,20 +457,27 @@ public override Column[] GetColumns(string table)
400457
}
401458
else if (column.Type == DbType.DateTime || column.Type == DbType.DateTime2)
402459
{
403-
if (column.DefaultValue is string defValCv && defValCv.StartsWith("CONVERT("))
460+
// (CONVERT([datetime],'2000-01-02 03:04:05.000',(121)))
461+
// 121 is a pattern: it contains milliseconds
462+
// Search for 121 here: https://learn.microsoft.com/de-de/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver17
463+
var regexDateTimeConvert121 = new Regex(@"(?<=^\(CONVERT\([\[]+datetime[\]]+,')[^']+(?='\s*,\s*\(121\s*\)\)\)$)");
464+
var match121 = regexDateTimeConvert121.Match(defaultValueString);
465+
466+
if (match121.Success)
404467
{
405-
var dt = defValCv.Substring((defValCv.IndexOf("'") + 1), defValCv.IndexOf("'", defValCv.IndexOf("'") + 1) - defValCv.IndexOf("'") - 1);
406-
var d = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
407-
column.DefaultValue = d;
468+
// We convert to UTC since we restrict date time default values to UTC on default value definition.
469+
column.DefaultValue = DateTime.ParseExact(match121.Value, "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
408470
}
409-
else if (column.DefaultValue is string defVal)
471+
else if (defaultValueString is string defVal)
410472
{
473+
// Not tested
411474
var dt = defVal;
412475
if (defVal.StartsWith("'"))
413476
{
414477
dt = defVal.Substring(1, defVal.Length - 2);
415478
}
416479

480+
// We convert to UTC since we restrict date time default values to UTC on default value definition.
417481
var d = DateTime.ParseExact(dt, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
418482
column.DefaultValue = d;
419483
}
@@ -427,6 +491,7 @@ public override Column[] GetColumns(string table)
427491
if (column.DefaultValue is string defVal)
428492
{
429493
var dt = defVal;
494+
430495
if (defVal.StartsWith("'"))
431496
{
432497
dt = defVal.Substring(1, defVal.Length - 2);
@@ -436,6 +501,17 @@ public override Column[] GetColumns(string table)
436501
column.DefaultValue = d;
437502
}
438503
}
504+
else if (column.MigratorDbType == MigratorDbType.Decimal)
505+
{
506+
// We assume ((1.234))
507+
var decimalString = defaultValueString.Replace("(", "").Replace(")", "");
508+
509+
column.DefaultValue = decimal.Parse(decimalString, CultureInfo.InvariantCulture);
510+
}
511+
else
512+
{
513+
throw new NotImplementedException($"Cannot parse the default value of {column.Name}. Type '' is not implemented yet.");
514+
}
439515
}
440516
if (!reader.IsDBNull(5))
441517
{

0 commit comments

Comments
 (0)