From 8a6143a4208321e1baa86110c10787437147b5c1 Mon Sep 17 00:00:00 2001 From: mjc Date: Thu, 7 Nov 2024 23:15:53 -0600 Subject: [PATCH] Refactored some naming conventions --- .../DataAnnotations/DxColumnAttribute.cs | 4 + .../DxPrimaryKeyConstraintAttribute.cs | 10 +- src/DapperMatic/DbConnectionExtensions.cs | 7 +- src/DapperMatic/DbProviderSqlType.cs | 74 ++ ...finity.cs => DbProviderSqlTypeAffinity.cs} | 6 +- src/DapperMatic/DbProviderType.cs | 51 + src/DapperMatic/DbProviderTypeExtensions.cs | 55 - src/DapperMatic/ExtensionMethods.cs | 34 + .../Interfaces/IDatabaseMethods.cs | 7 +- src/DapperMatic/Models/DxColumn.cs | 31 +- src/DapperMatic/Models/DxTableFactory.cs | 80 +- .../Base/DatabaseMethodsBase.Columns.cs | 17 +- .../Base/DatabaseMethodsBase.Strings.cs | 62 +- .../Base/DatabaseMethodsBase.Tables.cs | 16 +- .../Providers/Base/DatabaseMethodsBase.cs | 107 +- .../DbProviderDotnetTypeDescriptor.cs | 72 ++ .../Providers/DbProviderTypeMapBase.cs | 363 +++++++ .../{ProviderUtils.cs => DbProviderUtils.cs} | 3 +- .../Providers/IDbProviderTypeMap.cs | 14 + src/DapperMatic/Providers/IProviderTypeMap.cs | 18 - .../Providers/MySql/MySqlMethods.Strings.cs | 16 +- .../Providers/MySql/MySqlMethods.Tables.cs | 15 +- .../Providers/MySql/MySqlMethods.cs | 6 +- .../Providers/MySql/MySqlProviderTypeMap.cs | 541 ++++++++-- .../PostgreSql/PostgreSqlMethods.Strings.cs | 17 +- .../PostgreSql/PostgreSqlMethods.Tables.cs | 24 +- .../Providers/PostgreSql/PostgreSqlMethods.cs | 4 +- .../PostgreSql/PostgreSqlProviderTypeMap.cs | 561 +++++++--- .../Providers/PostgreSql/PostgreSqlTypes.cs | 12 +- src/DapperMatic/Providers/ProviderSqlType.cs | 74 -- .../Providers/ProviderTypeMapBase.cs | 992 ------------------ .../SqlServer/SqlServerMethods.Strings.cs | 79 +- .../SqlServer/SqlServerMethods.Tables.cs | 9 +- .../Providers/SqlServer/SqlServerMethods.cs | 4 +- .../SqlServer/SqlServerProviderTypeMap.cs | 241 ++++- .../Providers/Sqlite/SqliteMethods.Strings.cs | 5 +- .../Providers/Sqlite/SqliteMethods.cs | 4 +- .../Providers/Sqlite/SqliteProviderTypeMap.cs | 383 +++++-- .../Providers/Sqlite/SqliteSqlParser.cs | 32 +- .../DatabaseMethodsTests.Columns.cs | 297 ------ ...DatabaseMethodsTests.DefaultConstraints.cs | 2 +- .../DatabaseMethodsTests.TableFactory.cs | 179 ++++ .../DatabaseMethodsTests.Types.cs | 26 +- .../DapperMatic.Tests/ExtensionMethodTests.cs | 44 + 44 files changed, 2583 insertions(+), 2015 deletions(-) create mode 100644 src/DapperMatic/DbProviderSqlType.cs rename src/DapperMatic/{Providers/ProviderSqlTypeAffinity.cs => DbProviderSqlTypeAffinity.cs} (60%) delete mode 100644 src/DapperMatic/DbProviderTypeExtensions.cs create mode 100644 src/DapperMatic/Providers/DbProviderDotnetTypeDescriptor.cs create mode 100644 src/DapperMatic/Providers/DbProviderTypeMapBase.cs rename src/DapperMatic/Providers/{ProviderUtils.cs => DbProviderUtils.cs} (97%) create mode 100644 src/DapperMatic/Providers/IDbProviderTypeMap.cs delete mode 100644 src/DapperMatic/Providers/IProviderTypeMap.cs delete mode 100644 src/DapperMatic/Providers/ProviderSqlType.cs delete mode 100644 src/DapperMatic/Providers/ProviderTypeMapBase.cs create mode 100644 tests/DapperMatic.Tests/DatabaseMethodsTests.TableFactory.cs create mode 100644 tests/DapperMatic.Tests/ExtensionMethodTests.cs diff --git a/src/DapperMatic/DataAnnotations/DxColumnAttribute.cs b/src/DapperMatic/DataAnnotations/DxColumnAttribute.cs index 967ff7e..f60af8b 100644 --- a/src/DapperMatic/DataAnnotations/DxColumnAttribute.cs +++ b/src/DapperMatic/DataAnnotations/DxColumnAttribute.cs @@ -45,6 +45,10 @@ public DxColumnAttribute( } public string? ColumnName { get; } + + /// + /// /// Format of provider data types: {mysql:varchar(255),sqlserver:nvarchar(255)} + /// public string? ProviderDataType { get; } public int? Length { get; } public int? Precision { get; } diff --git a/src/DapperMatic/DataAnnotations/DxPrimaryKeyConstraintAttribute.cs b/src/DapperMatic/DataAnnotations/DxPrimaryKeyConstraintAttribute.cs index b088004..ccf81cb 100644 --- a/src/DapperMatic/DataAnnotations/DxPrimaryKeyConstraintAttribute.cs +++ b/src/DapperMatic/DataAnnotations/DxPrimaryKeyConstraintAttribute.cs @@ -2,10 +2,7 @@ namespace DapperMatic.DataAnnotations; -[AttributeUsage( - AttributeTargets.Property | AttributeTargets.Class, - AllowMultiple = true -)] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)] public class DxPrimaryKeyConstraintAttribute : Attribute { public DxPrimaryKeyConstraintAttribute() { } @@ -21,6 +18,11 @@ public DxPrimaryKeyConstraintAttribute(string constraintName, params string[] co Columns = columnNames.Select(columnName => new DxOrderedColumn(columnName)).ToArray(); } + public DxPrimaryKeyConstraintAttribute(string[] columnNames) + { + Columns = columnNames.Select(columnName => new DxOrderedColumn(columnName)).ToArray(); + } + public string? ConstraintName { get; } public DxOrderedColumn[]? Columns { get; } } diff --git a/src/DapperMatic/DbConnectionExtensions.cs b/src/DapperMatic/DbConnectionExtensions.cs index 235ff23..5a37cdc 100644 --- a/src/DapperMatic/DbConnectionExtensions.cs +++ b/src/DapperMatic/DbConnectionExtensions.cs @@ -30,12 +30,15 @@ public static async Task GetDatabaseVersionAsync( return await Database(db).GetDatabaseVersionAsync(db, tx, cancellationToken); } - public static IProviderTypeMap GetProviderTypeMap(this IDbConnection db) + public static IDbProviderTypeMap GetProviderTypeMap(this IDbConnection db) { return Database(db).ProviderTypeMap; } - public static (Type dotnetType, int? length, int? precision, int? scale, bool? autoIncrementing, Type[] allSupportedTypes) GetDotnetTypeFromSqlType(this IDbConnection db, string sqlType) + public static DbProviderDotnetTypeDescriptor GetDotnetTypeFromSqlType( + this IDbConnection db, + string sqlType + ) { return Database(db).GetDotnetTypeFromSqlType(sqlType); } diff --git a/src/DapperMatic/DbProviderSqlType.cs b/src/DapperMatic/DbProviderSqlType.cs new file mode 100644 index 0000000..78575fe --- /dev/null +++ b/src/DapperMatic/DbProviderSqlType.cs @@ -0,0 +1,74 @@ +namespace DapperMatic.Providers; + +/// +/// The provider SQL type. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +public class DbProviderSqlType( + DbProviderSqlTypeAffinity affinity, + string name, + Type? recommendedDotnetType = null, + string? aliasOf = null, + string? formatWithLength = null, + string? formatWithPrecision = null, + string? formatWithPrecisionAndScale = null, + int? defaultLength = null, + int? defaultPrecision = null, + int? defaultScale = null, + bool canUseToAutoIncrement = false, + bool autoIncrementsAutomatically = false, + double? minValue = null, + double? maxValue = null, + bool includesTimeZone = false, + bool isDateOnly = false, + bool isTimeOnly = false, + bool isYearOnly = false, + bool isFixedLength = false, + bool isGuidOnly = false, + bool isUnicode = false +) +{ + public DbProviderSqlTypeAffinity Affinity { get; init; } = affinity; + public string Name { get; init; } = name; + public Type? RecommendedDotnetType { get; init; } = recommendedDotnetType; + public string? AliasOf { get; set; } = aliasOf; + public string? FormatWithLength { get; init; } = formatWithLength; + public string? FormatWithPrecision { get; init; } = formatWithPrecision; + public string? FormatWithPrecisionAndScale { get; init; } = formatWithPrecisionAndScale; + public int? DefaultLength { get; set; } = defaultLength; + public int? DefaultPrecision { get; set; } = defaultPrecision; + public int? DefaultScale { get; set; } = defaultScale; + public bool CanUseToAutoIncrement { get; init; } = canUseToAutoIncrement; + public bool AutoIncrementsAutomatically { get; init; } = autoIncrementsAutomatically; + public double? MinValue { get; init; } = minValue; + public double? MaxValue { get; init; } = maxValue; + public bool IncludesTimeZone { get; init; } = includesTimeZone; + public bool IsDateOnly { get; init; } = isDateOnly; + public bool IsTimeOnly { get; init; } = isTimeOnly; + public bool IsYearOnly { get; init; } = isYearOnly; + public bool IsFixedLength { get; init; } = isFixedLength; + public bool IsGuidOnly { get; init; } = isGuidOnly; + public bool IsUnicode { get; set; } = isUnicode; +} + +public static class DbProviderSqlTypeExtensions +{ + public static bool SupportsLength(this DbProviderSqlType providerSqlType) => + !string.IsNullOrWhiteSpace(providerSqlType.FormatWithLength); + + public static bool SupportsPrecision(this DbProviderSqlType providerSqlType) => + !string.IsNullOrWhiteSpace(providerSqlType.FormatWithPrecision); + + public static bool SupportsPrecisionAndScale(this DbProviderSqlType providerSqlType) => + !string.IsNullOrWhiteSpace(providerSqlType.FormatWithPrecisionAndScale); +} diff --git a/src/DapperMatic/Providers/ProviderSqlTypeAffinity.cs b/src/DapperMatic/DbProviderSqlTypeAffinity.cs similarity index 60% rename from src/DapperMatic/Providers/ProviderSqlTypeAffinity.cs rename to src/DapperMatic/DbProviderSqlTypeAffinity.cs index 6b063cd..d4c9802 100644 --- a/src/DapperMatic/Providers/ProviderSqlTypeAffinity.cs +++ b/src/DapperMatic/DbProviderSqlTypeAffinity.cs @@ -1,6 +1,6 @@ -namespace DapperMatic.Providers; +namespace DapperMatic; -public enum ProviderSqlTypeAffinity +public enum DbProviderSqlTypeAffinity { Integer, Real, @@ -11,4 +11,4 @@ public enum ProviderSqlTypeAffinity Geometry, RangeType, Other -} \ No newline at end of file +} diff --git a/src/DapperMatic/DbProviderType.cs b/src/DapperMatic/DbProviderType.cs index 0e6e4cc..8f41292 100644 --- a/src/DapperMatic/DbProviderType.cs +++ b/src/DapperMatic/DbProviderType.cs @@ -1,3 +1,6 @@ +using System.Collections.Concurrent; +using System.Data; + namespace DapperMatic; public enum DbProviderType @@ -7,3 +10,51 @@ public enum DbProviderType MySql, PostgreSql } + +public static class DbProviderTypeExtensions +{ + private static readonly ConcurrentDictionary ProviderTypes = new(); + + public static DbProviderType GetDbProviderType(this IDbConnection db) + { + var type = db.GetType(); + if (ProviderTypes.TryGetValue(type, out var dbType)) + { + return dbType; + } + + dbType = ToDbProviderType(type.FullName!); + ProviderTypes.TryAdd(type, dbType); + + return dbType; + } + + private static DbProviderType ToDbProviderType(string provider) + { + if (provider.Contains("sqlite", StringComparison.OrdinalIgnoreCase)) + return DbProviderType.Sqlite; + + if ( + provider.Contains("mysql", StringComparison.OrdinalIgnoreCase) + || provider.Contains("maria", StringComparison.OrdinalIgnoreCase) + ) + return DbProviderType.MySql; + + if ( + provider.Contains("postgres", StringComparison.OrdinalIgnoreCase) + || provider.Contains("npgsql", StringComparison.OrdinalIgnoreCase) + || provider.Contains("pg", StringComparison.OrdinalIgnoreCase) + ) + return DbProviderType.PostgreSql; + + if ( + provider.Contains("sqlserver", StringComparison.OrdinalIgnoreCase) + || provider.Contains("mssql", StringComparison.OrdinalIgnoreCase) + || provider.Contains("localdb", StringComparison.OrdinalIgnoreCase) + || provider.Contains("sqlclient", StringComparison.OrdinalIgnoreCase) + ) + return DbProviderType.SqlServer; + + throw new NotSupportedException($"Db type {provider} is not supported."); + } +} diff --git a/src/DapperMatic/DbProviderTypeExtensions.cs b/src/DapperMatic/DbProviderTypeExtensions.cs deleted file mode 100644 index 3b4cc4c..0000000 --- a/src/DapperMatic/DbProviderTypeExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Concurrent; -using System.Data; - -namespace DapperMatic; - -public static class DbProviderTypeExtensions -{ - private static readonly ConcurrentDictionary ProviderTypes = new(); - - public static DbProviderType GetDbProviderType(this IDbConnection db) - { - var type = db.GetType(); - if (ProviderTypes.TryGetValue(type, out var dbType)) - { - return dbType; - } - - dbType = ToDbProviderType(type.FullName!); - ProviderTypes.TryAdd(type, dbType); - - return dbType; - } - - private static DbProviderType ToDbProviderType(string provider) - { - if ( - string.IsNullOrWhiteSpace(provider) - || provider.Contains("sqlite", StringComparison.OrdinalIgnoreCase) - ) - return DbProviderType.Sqlite; - - if ( - provider.Contains("mysql", StringComparison.OrdinalIgnoreCase) - || provider.Contains("maria", StringComparison.OrdinalIgnoreCase) - ) - return DbProviderType.MySql; - - if ( - provider.Contains("postgres", StringComparison.OrdinalIgnoreCase) - || provider.Contains("npgsql", StringComparison.OrdinalIgnoreCase) - || provider.Contains("pg", StringComparison.OrdinalIgnoreCase) - ) - return DbProviderType.PostgreSql; - - if ( - provider.Contains("sqlserver", StringComparison.OrdinalIgnoreCase) - || provider.Contains("mssql", StringComparison.OrdinalIgnoreCase) - || provider.Contains("localdb", StringComparison.OrdinalIgnoreCase) - || provider.Contains("sqlclient", StringComparison.OrdinalIgnoreCase) - ) - return DbProviderType.SqlServer; - - throw new NotSupportedException($"Cache type {provider} is not supported."); - } -} diff --git a/src/DapperMatic/ExtensionMethods.cs b/src/DapperMatic/ExtensionMethods.cs index 2f7cdc6..b5afe8f 100644 --- a/src/DapperMatic/ExtensionMethods.cs +++ b/src/DapperMatic/ExtensionMethods.cs @@ -9,6 +9,24 @@ namespace DapperMatic; [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static partial class ExtensionMethods { + public static string GetFriendlyName(this Type type) + { + if (type == null) + return "(Unknown Type)"; + + if (!type.IsGenericType) + return type.Name; + + var genericTypeName = type.GetGenericTypeDefinition().Name; + var friendlyGenericTypeName = genericTypeName[..genericTypeName.LastIndexOf("`")]; + + var genericArguments = type.GetGenericArguments(); + var genericArgumentNames = genericArguments.Select(GetFriendlyName).ToArray(); + var genericTypeArgumentsString = string.Join(", ", genericArgumentNames); + + return $"{friendlyGenericTypeName}<{genericTypeArgumentsString}>"; + } + public static TValue? GetFieldValue(this object instance, string name) { var type = instance.GetType(); @@ -72,6 +90,22 @@ public static int[] ExtractNumbers(this string input) return [.. numbers]; } + public static string DiscardLengthPrecisionAndScaleFromSqlTypeName(this string sqlTypeName) + { + // extract the type name from the sql type name where a sqlTypeName might be "time(5, 2) without time zone" and the return value would be "time without time zone", + // it could also be "time ( 122, 2 ) without time zone" and the return value would be "time without time zone + var openIndex = sqlTypeName.IndexOf('('); + var closeIndex = sqlTypeName.IndexOf(')'); + var txt = ( + openIndex > 0 && closeIndex > 0 + ? sqlTypeName.Remove(openIndex, closeIndex - openIndex + 1) + : sqlTypeName + ).Trim(); + while (txt.Contains(" ")) + txt = txt.Replace(" ", " "); + return txt; + } + public static string ToQuotedIdentifier( this string prefix, char[] quoteChar, diff --git a/src/DapperMatic/Interfaces/IDatabaseMethods.cs b/src/DapperMatic/Interfaces/IDatabaseMethods.cs index af263a8..c7a3154 100644 --- a/src/DapperMatic/Interfaces/IDatabaseMethods.cs +++ b/src/DapperMatic/Interfaces/IDatabaseMethods.cs @@ -16,7 +16,7 @@ public interface IDatabaseMethods IDatabaseViewMethods { DbProviderType ProviderType { get; } - IProviderTypeMap ProviderTypeMap { get; } + IDbProviderTypeMap ProviderTypeMap { get; } bool SupportsSchemas { get; } @@ -39,9 +39,8 @@ Task GetDatabaseVersionAsync( CancellationToken cancellationToken = default ); - (Type dotnetType, int? length, int? precision, int? scale, bool? isAutoIncrementing, Type[] allSupportedTypes) - GetDotnetTypeFromSqlType(string sqlType); - string GetSqlTypeFromDotnetType(Type type, int? length, int? precision, int? scale, bool? autoIncrementing); + DbProviderDotnetTypeDescriptor GetDotnetTypeFromSqlType(string sqlType); + string GetSqlTypeFromDotnetType(DbProviderDotnetTypeDescriptor descriptor); string NormalizeName(string name); } diff --git a/src/DapperMatic/Models/DxColumn.cs b/src/DapperMatic/Models/DxColumn.cs index 7ecbc0d..ee47f5d 100644 --- a/src/DapperMatic/Models/DxColumn.cs +++ b/src/DapperMatic/Models/DxColumn.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.Json; namespace DapperMatic.Models; @@ -16,7 +18,7 @@ public DxColumn( string tableName, string columnName, Type dotnetType, - string? providerDataType = null, + Dictionary? providerDataTypes = null, int? length = null, int? precision = null, int? scale = null, @@ -38,13 +40,8 @@ public DxColumn( TableName = tableName; ColumnName = columnName; DotnetType = dotnetType; - ProviderDataType = providerDataType; - Length = - dotnetType == typeof(string) - && string.IsNullOrWhiteSpace(providerDataType) - && !length.HasValue - ? 255 /* a sensible default */ - : length; + ProviderDataTypes = providerDataTypes ?? []; + Length = length; Precision = precision; Scale = scale; CheckExpression = checkExpression; @@ -73,7 +70,7 @@ public DxColumn( /// /// The provider data type should include the length, precision, and scale if applicable. /// - public string? ProviderDataType { get; set; } + public Dictionary ProviderDataTypes { get; } = new(); public int? Length { get; set; } public int? Precision { get; set; } public int? Scale { get; set; } @@ -82,6 +79,7 @@ public DxColumn( public bool IsNullable { get; set; } public bool IsPrimaryKey { get; set; } public bool IsAutoIncrement { get; set; } + public bool IsUnicode { get; set; } /// /// Is either part of a single column unique constraint or a single column unique index. @@ -193,7 +191,7 @@ public string GetTypeCategory() // ToString override to display column definition public override string ToString() { - return $"{ColumnName} ({ProviderDataType}) {(IsNullable ? "NULL" : "NOT NULL")}" + return $"{ColumnName} ({JsonSerializer.Serialize(ProviderDataTypes)}) {(IsNullable ? "NULL" : "NOT NULL")}" + $"{(IsPrimaryKey ? " PRIMARY KEY" : "")}" + $"{(IsUnique ? " UNIQUE" : "")}" + $"{(IsIndexed ? " INDEXED" : "")}" @@ -202,4 +200,17 @@ public override string ToString() + $"{(!string.IsNullOrWhiteSpace(CheckExpression) ? $" CHECK ({CheckExpression})" : "")}" + $"{(!string.IsNullOrWhiteSpace(DefaultExpression) ? $" DEFAULT {(DefaultExpression.Contains(' ') ? $"({DefaultExpression})" : DefaultExpression)}" : "")}"; } + + public string? GetProviderDataType(DbProviderType providerType) + { + return ProviderDataTypes.TryGetValue(providerType, out var providerDataType) + ? providerDataType + : null; + } + + public DxColumn SetProviderDataType(DbProviderType providerType, string providerDataType) + { + ProviderDataTypes[providerType] = providerDataType; + return this; + } } diff --git a/src/DapperMatic/Models/DxTableFactory.cs b/src/DapperMatic/Models/DxTableFactory.cs index d9b578d..fb4fec7 100644 --- a/src/DapperMatic/Models/DxTableFactory.cs +++ b/src/DapperMatic/Models/DxTableFactory.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.ComponentModel.DataAnnotations; using System.Data; using System.Reflection; using DapperMatic.DataAnnotations; @@ -188,7 +187,7 @@ ca is System.ComponentModel.DataAnnotations.Schema.TableAttribute ta var paType = pa.GetType(); return pa is DxPrimaryKeyConstraintAttribute // EF Core - || pa is KeyAttribute + || pa is System.ComponentModel.DataAnnotations.KeyAttribute // ServiceStack.OrmLite || paType.Name == "PrimaryKeyAttribute"; }); @@ -203,12 +202,57 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute || paType.Name == "RequiredAttribute"; }); + // Format of provider data types: {mysql:varchar(255),sqlserver:nvarchar(255)} + var providerDataTypes = new Dictionary(); + if (!string.IsNullOrWhiteSpace(columnAttribute?.ProviderDataType)) + { + var pdts = columnAttribute + .ProviderDataType.Trim('{', '}', '[', ']', ' ') + .Split([',', ';']); + foreach (var pdt in pdts) + { + var pdtParts = pdt.Split(':'); + if ( + pdtParts.Length == 2 + && !string.IsNullOrWhiteSpace(pdtParts[0]) + && !string.IsNullOrWhiteSpace(pdtParts[1]) + ) + { + if ( + pdtParts[0].Contains("mysql", StringComparison.OrdinalIgnoreCase) + || pdtParts[0].Contains("maria", StringComparison.OrdinalIgnoreCase) + ) + { + providerDataTypes.Add(DbProviderType.MySql, pdtParts[1]); + } + else if ( + pdtParts[0].Contains("pg", StringComparison.OrdinalIgnoreCase) + || pdtParts[0].Contains("postgres", StringComparison.OrdinalIgnoreCase) + ) + { + providerDataTypes.Add(DbProviderType.PostgreSql, pdtParts[1]); + } + else if (pdtParts[0].Contains("sqlite", StringComparison.OrdinalIgnoreCase)) + { + providerDataTypes.Add(DbProviderType.Sqlite, pdtParts[1]); + } + else if ( + pdtParts[0].Contains("sqlserver", StringComparison.OrdinalIgnoreCase) + || pdtParts[0].Contains("mssql", StringComparison.OrdinalIgnoreCase) + ) + { + providerDataTypes.Add(DbProviderType.SqlServer, pdtParts[1]); + } + } + } + } + var column = new DxColumn( schemaName, tableName, columnName, property.PropertyType, - columnAttribute?.ProviderDataType, + providerDataTypes.Count != 0 ? providerDataTypes : null, columnAttribute?.Length, columnAttribute?.Precision, columnAttribute?.Scale, @@ -238,14 +282,16 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute if (column.Length == null) { - var stringLengthAttribute = property.GetCustomAttribute(); + var stringLengthAttribute = + property.GetCustomAttribute(); if (stringLengthAttribute != null) { column.Length = stringLengthAttribute.MaximumLength; } else { - var maxLengthAttribute = property.GetCustomAttribute(); + var maxLengthAttribute = + property.GetCustomAttribute(); if (maxLengthAttribute != null) { column.Length = maxLengthAttribute.Length; @@ -314,7 +360,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute columnName, !string.IsNullOrWhiteSpace(columnCheckConstraintAttribute.ConstraintName) ? columnCheckConstraintAttribute.ConstraintName - : ProviderUtils.GenerateCheckConstraintName(tableName, columnName), + : DbProviderUtils.GenerateCheckConstraintName(tableName, columnName), columnCheckConstraintAttribute.Expression ); checkConstraints.Add(checkConstraint); @@ -333,7 +379,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute columnName, !string.IsNullOrWhiteSpace(columnDefaultConstraintAttribute.ConstraintName) ? columnDefaultConstraintAttribute.ConstraintName - : ProviderUtils.GenerateDefaultConstraintName(tableName, columnName), + : DbProviderUtils.GenerateDefaultConstraintName(tableName, columnName), columnDefaultConstraintAttribute.Expression ); defaultConstraints.Add(defaultConstraint); @@ -351,7 +397,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute tableName, !string.IsNullOrWhiteSpace(columnUniqueConstraintAttribute.ConstraintName) ? columnUniqueConstraintAttribute.ConstraintName - : ProviderUtils.GenerateUniqueConstraintName(tableName, columnName), + : DbProviderUtils.GenerateUniqueConstraintName(tableName, columnName), [new(columnName)] ); uniqueConstraints.Add(uniqueConstraint); @@ -368,7 +414,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute tableName, !string.IsNullOrWhiteSpace(columnIndexAttribute.IndexName) ? columnIndexAttribute.IndexName - : ProviderUtils.GenerateIndexName(tableName, columnName), + : DbProviderUtils.GenerateIndexName(tableName, columnName), [new(columnName)], isUnique: columnIndexAttribute.IsUnique ); @@ -393,7 +439,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute && !string.IsNullOrWhiteSpace(n) ) ? n - : ProviderUtils.GenerateIndexName(tableName, columnName); + : DbProviderUtils.GenerateIndexName(tableName, columnName); var index = new DxIndex( schemaName, tableName, @@ -430,7 +476,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute columnForeignKeyConstraintAttribute.ConstraintName ) ? columnForeignKeyConstraintAttribute.ConstraintName - : ProviderUtils.GenerateForeignKeyConstraintName( + : DbProviderUtils.GenerateForeignKeyConstraintName( tableName, columnName, referencedTableName, @@ -471,7 +517,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute }; var onDelete = DxForeignKeyAction.NoAction; var onUpdate = DxForeignKeyAction.NoAction; - var constraintName = ProviderUtils.GenerateForeignKeyConstraintName( + var constraintName = DbProviderUtils.GenerateForeignKeyConstraintName( tableName, columnName, referencedTableName, @@ -533,7 +579,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute if (primaryKey != null && string.IsNullOrWhiteSpace(primaryKey.ConstraintName)) { - primaryKey.ConstraintName = ProviderUtils.GeneratePrimaryKeyConstraintName( + primaryKey.ConstraintName = DbProviderUtils.GeneratePrimaryKeyConstraintName( tableName, primaryKey.Columns.Select(c => c.ColumnName).ToArray() ); @@ -548,7 +594,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute var constraintName = !string.IsNullOrWhiteSpace(cca.ConstraintName) ? cca.ConstraintName - : ProviderUtils.GenerateCheckConstraintName(tableName, $"{ccaId++}"); + : DbProviderUtils.GenerateCheckConstraintName(tableName, $"{ccaId++}"); checkConstraints.Add( new DxCheckConstraint(schemaName, tableName, null, constraintName, cca.Expression) @@ -563,7 +609,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute var constraintName = !string.IsNullOrWhiteSpace(uca.ConstraintName) ? uca.ConstraintName - : ProviderUtils.GenerateUniqueConstraintName( + : DbProviderUtils.GenerateUniqueConstraintName( tableName, uca.Columns.Select(c => c.ColumnName).ToArray() ); @@ -593,7 +639,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute var indexName = !string.IsNullOrWhiteSpace(cia.IndexName) ? cia.IndexName - : ProviderUtils.GenerateIndexName( + : DbProviderUtils.GenerateIndexName( tableName, cia.Columns.Select(c => c.ColumnName).ToArray() ); @@ -634,7 +680,7 @@ pa is System.ComponentModel.DataAnnotations.RequiredAttribute var constraintName = !string.IsNullOrWhiteSpace(cfk.ConstraintName) ? cfk.ConstraintName - : ProviderUtils.GenerateForeignKeyConstraintName( + : DbProviderUtils.GenerateForeignKeyConstraintName( tableName, cfk.SourceColumnNames, cfk.ReferencedTableName, diff --git a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Columns.cs b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Columns.cs index 2a42a5b..c8e1a39 100644 --- a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Columns.cs +++ b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Columns.cs @@ -16,7 +16,7 @@ public virtual async Task DoesColumnExistAsync( ) { return await GetColumnAsync(db, schemaName, tableName, columnName, tx, cancellationToken) - .ConfigureAwait(false) != null; + .ConfigureAwait(false) != null; } public virtual async Task CreateColumnIfNotExistsAsync( @@ -175,7 +175,12 @@ public virtual async Task CreateColumnIfNotExistsAsync( tableName, columnName, dotnetType, - providerDataType, + providerDataType == null + ? null + : new Dictionary + { + { ProviderType, providerDataType } + }, length, precision, scale, @@ -408,10 +413,10 @@ await DoesColumnExistAsync( await ExecuteAsync( db, $""" - ALTER TABLE {schemaQualifiedTableName} - RENAME COLUMN {columnName} - TO {newColumnName} - """, + ALTER TABLE {schemaQualifiedTableName} + RENAME COLUMN {columnName} + TO {newColumnName} + """, tx: tx ) .ConfigureAwait(false); diff --git a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Strings.cs b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Strings.cs index 67eed0c..a72d65f 100644 --- a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Strings.cs +++ b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Strings.cs @@ -149,13 +149,13 @@ Version dbVersion ) ) { - var pkConstraintName = ProviderUtils.GeneratePrimaryKeyConstraintName( + var pkConstraintName = DbProviderUtils.GeneratePrimaryKeyConstraintName( tableName, columnName ); var pkInlineSql = SqlInlinePrimaryKeyColumnConstraint( + column, pkConstraintName, - column.IsAutoIncrement, out var useTableConstraint ); if (!string.IsNullOrWhiteSpace(pkInlineSql)) @@ -190,7 +190,7 @@ [new DxOrderedColumn(columnName)] ) ) { - var defConstraintName = ProviderUtils.GenerateDefaultConstraintName( + var defConstraintName = DbProviderUtils.GenerateDefaultConstraintName( tableName, columnName ); @@ -223,7 +223,10 @@ [new DxOrderedColumn(columnName)] ) ) { - var ckConstraintName = ProviderUtils.GenerateCheckConstraintName(tableName, columnName); + var ckConstraintName = DbProviderUtils.GenerateCheckConstraintName( + tableName, + columnName + ); var ckInlineSql = SqlInlineCheckColumnConstraint( ckConstraintName, column.CheckExpression, @@ -257,7 +260,7 @@ out var useTableConstraint ) ) { - var ucConstraintName = ProviderUtils.GenerateUniqueConstraintName( + var ucConstraintName = DbProviderUtils.GenerateUniqueConstraintName( tableName, columnName ); @@ -293,7 +296,7 @@ [new DxOrderedColumn(columnName)] ) ) { - var fkConstraintName = ProviderUtils.GenerateForeignKeyConstraintName( + var fkConstraintName = DbProviderUtils.GenerateForeignKeyConstraintName( tableName, columnName, NormalizeName(column.ReferencedTableName), @@ -338,7 +341,7 @@ [new DxOrderedColumn(column.ReferencedColumnName)], ) ) { - var indexName = ProviderUtils.GenerateIndexName(tableName, columnName); + var indexName = DbProviderUtils.GenerateIndexName(tableName, columnName); tableConstraints.Indexes.Add( new DxIndex( schemaName, @@ -355,37 +358,48 @@ [new DxOrderedColumn(columnName)], protected virtual string SqlInlineColumnNameAndType(DxColumn column, Version dbVersion) { - var columnType = string.IsNullOrWhiteSpace(column.ProviderDataType) - ? GetSqlTypeFromDotnetType( - column.DotnetType, - column.Length, - column.Precision, - column.Scale - ) - : column.ProviderDataType; + var descriptor = new DbProviderDotnetTypeDescriptor( + column.DotnetType, + column.Length, + column.Precision, + column.Scale, + column.IsAutoIncrement, + column.IsUnicode + ); + + var columnType = string.IsNullOrWhiteSpace(column.GetProviderDataType(ProviderType)) + ? GetSqlTypeFromDotnetType(descriptor) + : column.GetProviderDataType(ProviderType); + + if (string.IsNullOrWhiteSpace(columnType)) + throw new InvalidOperationException( + $"Could not determine the SQL type for column {column.ColumnName} of type {column.DotnetType.Name}" + ); // set the type on the column so that it can be used in other methods - column.ProviderDataType = columnType; + column.SetProviderDataType(ProviderType, columnType); return $"{NormalizeName(column.ColumnName)} {columnType}"; } protected virtual string SqlInlineColumnNullable(DxColumn column) { - return column.IsNullable ? " NULL" : " NOT NULL"; + return column.IsNullable && !column.IsUnique && !column.IsPrimaryKey + ? " NULL" + : " NOT NULL"; } protected virtual string SqlInlinePrimaryKeyColumnConstraint( + DxColumn column, string constraintName, - bool isAutoIncrement, out bool useTableConstraint ) { useTableConstraint = false; - return $"CONSTRAINT {NormalizeName(constraintName)} PRIMARY KEY {(isAutoIncrement ? SqlInlinePrimaryKeyAutoIncrementColumnConstraint() : "")}".Trim(); + return $"CONSTRAINT {NormalizeName(constraintName)} PRIMARY KEY {(column.IsAutoIncrement ? SqlInlinePrimaryKeyAutoIncrementColumnConstraint(column) : "")}".Trim(); } - protected virtual string SqlInlinePrimaryKeyAutoIncrementColumnConstraint() + protected virtual string SqlInlinePrimaryKeyAutoIncrementColumnConstraint(DxColumn column) { return "IDENTITY(1,1)"; } @@ -448,7 +462,7 @@ DxPrimaryKeyConstraint primaryKeyConstraint var pkColumnNames = primaryKeyConstraint.Columns.Select(c => c.ColumnName).ToArray(); var pkConstrainName = !string.IsNullOrWhiteSpace(primaryKeyConstraint.ConstraintName) ? primaryKeyConstraint.ConstraintName - : ProviderUtils.GeneratePrimaryKeyConstraintName( + : DbProviderUtils.GeneratePrimaryKeyConstraintName( table.TableName, pkColumnNames.ToArray() ); @@ -461,11 +475,11 @@ protected virtual string SqlInlineCheckTableConstraint(DxTable table, DxCheckCon var ckConstraintName = !string.IsNullOrWhiteSpace(check.ConstraintName) ? check.ConstraintName : string.IsNullOrWhiteSpace(check.ColumnName) - ? ProviderUtils.GenerateCheckConstraintName( + ? DbProviderUtils.GenerateCheckConstraintName( table.TableName, DateTime.Now.Ticks.ToString() ) - : ProviderUtils.GenerateCheckConstraintName(table.TableName, check.ColumnName); + : DbProviderUtils.GenerateCheckConstraintName(table.TableName, check.ColumnName); return $"CONSTRAINT {NormalizeName(ckConstraintName)} CHECK ({check.Expression})"; } @@ -494,7 +508,7 @@ bool supportsOrderedKeysInConstraints { var ucConstraintName = !string.IsNullOrWhiteSpace(uc.ConstraintName) ? uc.ConstraintName - : ProviderUtils.GenerateUniqueConstraintName( + : DbProviderUtils.GenerateUniqueConstraintName( table.TableName, uc.Columns.Select(c => NormalizeName(c.ColumnName)).ToArray() ); diff --git a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Tables.cs b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Tables.cs index 45a5609..490c2c6 100644 --- a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Tables.cs +++ b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.Tables.cs @@ -145,7 +145,7 @@ [.. table.Indexes] if (table.PrimaryKeyConstraint == null && table.Columns.Count(c => c.IsPrimaryKey) > 1) { var pkColumns = table.Columns.Where(c => c.IsPrimaryKey).ToArray(); - var pkConstraintName = ProviderUtils.GeneratePrimaryKeyConstraintName( + var pkConstraintName = DbProviderUtils.GeneratePrimaryKeyConstraintName( table.TableName, pkColumns.Select(c => c.ColumnName).ToArray() ); @@ -185,7 +185,7 @@ [.. table.Indexes] ) ) { - var fkConstraintName = ProviderUtils.GenerateForeignKeyConstraintName( + var fkConstraintName = DbProviderUtils.GenerateForeignKeyConstraintName( tableName, column.ColumnName, column.ReferencedTableName, @@ -257,7 +257,17 @@ [new DxOrderedColumn(column.ReferencedColumnName)] } sql.AppendLine(); - sql.Append(");"); + sql.Append(")"); + + // TODO: for MySQL, we need to add the ENGINE=InnoDB; at the end of the CREATE TABLE statement + if (ProviderType == DbProviderType.MySql) + { + sql.Append( + " DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB" + ); + } + + sql.Append(';'); var sqlStatement = sql.ToString(); diff --git a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.cs b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.cs index a43db66..23234e2 100644 --- a/src/DapperMatic/Providers/Base/DatabaseMethodsBase.cs +++ b/src/DapperMatic/Providers/Base/DatabaseMethodsBase.cs @@ -12,7 +12,7 @@ public abstract partial class DatabaseMethodsBase : IDatabaseMethods { public abstract DbProviderType ProviderType { get; } - public abstract IProviderTypeMap ProviderTypeMap { get; } + public abstract IDbProviderTypeMap ProviderTypeMap { get; } protected abstract string DefaultSchema { get; } @@ -38,51 +38,74 @@ public virtual Task SupportsDefaultConstraintsAsync( private ILogger Logger => DxLogger.CreateLogger(GetType()); - public virtual (Type dotnetType, int? length, int? precision, int? scale, bool? isAutoIncrementing, Type[] - allSupportedTypes) GetDotnetTypeFromSqlType(string sqlType) + public virtual DbProviderDotnetTypeDescriptor GetDotnetTypeFromSqlType(string sqlType) { if ( - !ProviderTypeMap.TryGetRecommendedDotnetTypeMatchingSqlType( + !ProviderTypeMap.TryGetDotnetTypeDescriptorMatchingFullSqlTypeName( sqlType, - out var providerDataType - ) || !providerDataType.HasValue + out var dotnetTypeDescriptor + ) + || dotnetTypeDescriptor == null ) throw new NotSupportedException($"SQL type {sqlType} is not supported."); - return providerDataType.Value; + return dotnetTypeDescriptor; } - public string GetSqlTypeFromDotnetType( - Type type, - int? length = null, - int? precision = null, - int? scale = null, - bool? autoIncrementing = null - ) + public string GetSqlTypeFromDotnetType(DbProviderDotnetTypeDescriptor descriptor) { + var tmb = ProviderTypeMap as DbProviderTypeMapBase; + if ( - !ProviderTypeMap.TryGetRecommendedSqlTypeMatchingDotnetType( - type, - length, - precision, - scale, - autoIncrementing, + !ProviderTypeMap.TryGetProviderSqlTypeMatchingDotnetType( + descriptor, out var providerDataType ) || providerDataType == null ) - throw new NotSupportedException($"No provider data type found for .NET type {type}."); + { + if (tmb != null) + return tmb.SqTypeForUnknownDotnetType; + + throw new NotSupportedException( + $"No provider data type found for .NET type {descriptor}." + ); + } + + var length = descriptor.Length; + var precision = descriptor.Precision; + var scale = descriptor.Scale; if (providerDataType.SupportsLength()) { - length ??= providerDataType.DefaultLength; + if (!length.HasValue && descriptor.DotnetType == typeof(Guid)) + length = 36; + if (!length.HasValue && descriptor.DotnetType == typeof(char)) + length = 1; + if (!length.HasValue) + length = providerDataType.DefaultLength; + if (length.HasValue) { + if ( + tmb != null + && length >= 8000 + && providerDataType.Affinity == DbProviderSqlTypeAffinity.Text + ) + return tmb.SqTypeForStringLengthMax; + + if ( + tmb != null + && length >= 8000 + && providerDataType.Affinity == DbProviderSqlTypeAffinity.Binary + ) + return tmb.SqTypeForBinaryLengthMax; + if (!string.IsNullOrWhiteSpace(providerDataType.FormatWithLength)) return string.Format(providerDataType.FormatWithLength, length); } } - + if (providerDataType.SupportsPrecision()) { precision ??= providerDataType.DefaultPrecision; @@ -105,10 +128,8 @@ out var providerDataType return providerDataType.Name; } - internal static readonly ConcurrentDictionary< - string, - (string sql, object? parameters) - > LastSqls = new(); + internal readonly ConcurrentDictionary LastSqls = + new(); public abstract Task GetDatabaseVersionAsync( IDbConnection db, @@ -126,7 +147,7 @@ public string GetLastSql(IDbConnection db) return LastSqls.TryGetValue(db.ConnectionString, out var sql) ? sql : ("", null); } - private static void SetLastSql(IDbConnection db, string sql, object? param = null) + private void SetLastSql(IDbConnection db, string sql, object? param = null) { LastSqls.AddOrUpdate(db.ConnectionString, (sql, param), (_, _) => (sql, param)); } @@ -143,7 +164,7 @@ protected virtual async Task> QueryAsync( try { Log( - LogLevel.Information, + LogLevel.Debug, "[{provider}] Executing SQL query: {sql}, with parameters {parameters}", ProviderType, sql, @@ -161,10 +182,12 @@ await db.QueryAsync(sql, param, tx, commandTimeout, commandType) Log( LogLevel.Error, ex, - "An error occurred while executing SQL query: {sql}, with parameters {parameters}.\n{message}", + "An error occurred while executing {provider} SQL query with map {providerMap}: \n{message}\n{sql}, with parameters {parameters}.", + ProviderType, + ProviderTypeMap.GetType().Name, + ex.Message, sql, - param == null ? "{}" : JsonSerializer.Serialize(param), - ex.Message + param == null ? "{}" : JsonSerializer.Serialize(param) ); throw; } @@ -182,7 +205,7 @@ await db.QueryAsync(sql, param, tx, commandTimeout, commandType) try { Log( - LogLevel.Information, + LogLevel.Debug, "[{provider}] Executing SQL scalar: {sql}, with parameters {parameters}", ProviderType, sql, @@ -203,10 +226,12 @@ await db.QueryAsync(sql, param, tx, commandTimeout, commandType) Log( LogLevel.Error, ex, - "An error occurred while executing SQL scalar query: {sql}, with parameters {parameters}.\n{message}", + "An error occurred while executing {provider} SQL scalar query with map {providerMap}: \n{message}\n{sql}, with parameters {parameters}.", + ProviderType, + ProviderTypeMap.GetType().Name, + ex.Message, sql, - param == null ? "{}" : JsonSerializer.Serialize(param), - ex.Message + param == null ? "{}" : JsonSerializer.Serialize(param) ); throw; } @@ -224,7 +249,7 @@ protected virtual async Task ExecuteAsync( try { Log( - LogLevel.Information, + LogLevel.Debug, "[{provider}] Executing SQL statement: {sql}, with parameters {parameters}", ProviderType, sql, @@ -239,10 +264,12 @@ protected virtual async Task ExecuteAsync( Log( LogLevel.Error, ex, - "An error occurred while executing SQL statement: {sql}, with parameters {parameters}.\n{message}", + "An error occurred while executing {provider} SQL statement with map {providerMap}: \n{message}\n{sql}, with parameters {parameters}.", + ProviderType, + ProviderTypeMap.GetType().Name, + ex.Message, sql, - param == null ? "{}" : JsonSerializer.Serialize(param), - ex.Message + param == null ? "{}" : JsonSerializer.Serialize(param) ); throw; } diff --git a/src/DapperMatic/Providers/DbProviderDotnetTypeDescriptor.cs b/src/DapperMatic/Providers/DbProviderDotnetTypeDescriptor.cs new file mode 100644 index 0000000..514bf99 --- /dev/null +++ b/src/DapperMatic/Providers/DbProviderDotnetTypeDescriptor.cs @@ -0,0 +1,72 @@ +using System.Text; + +namespace DapperMatic.Providers; + +// The struct to store the parameters: +public class DbProviderDotnetTypeDescriptor +{ + public DbProviderDotnetTypeDescriptor( + Type dotnetType, + int? length = null, + int? precision = null, + int? scale = null, + bool? autoIncrement = false, + bool? unicode = false, + Type[]? otherSupportedTypes = null + ) + { + DotnetType = + ( + dotnetType.IsGenericType + && dotnetType.GetGenericTypeDefinition() == typeof(Nullable<>) + ) + ? Nullable.GetUnderlyingType(dotnetType)! + : dotnetType; + Length = length; + Precision = precision; + Scale = scale; + AutoIncrement = autoIncrement; + Unicode = unicode; + this.otherSupportedTypes = otherSupportedTypes ?? []; + } + + /// + /// Non-nullable type used to determine or map to a recommended sql type + /// + public Type DotnetType { get; init; } + public int? Length { get; init; } + public int? Precision { get; init; } + public int? Scale { get; init; } + public bool? AutoIncrement { get; init; } + public bool? Unicode { get; init; } + + private Type[] otherSupportedTypes = []; + public Type[] AllSupportedTypes + { + get => [DotnetType, .. otherSupportedTypes.Where(t => t != DotnetType).Distinct()]; + set => otherSupportedTypes = value; + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(DotnetType.GetFriendlyName()); + if (Length.HasValue) + { + sb.Append($" LENGTH({Length})"); + } + if (Precision.HasValue) + { + sb.Append($" PRECISION({Precision})"); + } + if (AutoIncrement.HasValue) + { + sb.Append(" AUTO_INCREMENT"); + } + if (Unicode.HasValue) + { + sb.Append(" UNICODE"); + } + return sb.ToString(); + } +} diff --git a/src/DapperMatic/Providers/DbProviderTypeMapBase.cs b/src/DapperMatic/Providers/DbProviderTypeMapBase.cs new file mode 100644 index 0000000..06b14ed --- /dev/null +++ b/src/DapperMatic/Providers/DbProviderTypeMapBase.cs @@ -0,0 +1,363 @@ +using System.Collections.Concurrent; + +namespace DapperMatic.Providers; + +public abstract class DbProviderTypeMapBase : IDbProviderTypeMap +{ + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once CollectionNeverUpdated.Global + public static readonly ConcurrentDictionary> TypeMaps = + new(); + + protected abstract DbProviderType ProviderType { get; } + protected abstract DbProviderSqlType[] ProviderSqlTypes { get; } + + private Dictionary? _lookup = null; + protected virtual Dictionary ProviderSqlTypeLookup => + _lookup ??= ProviderSqlTypes.ToDictionary(t => t.Name, StringComparer.OrdinalIgnoreCase); + + public abstract string SqTypeForStringLengthMax { get; } + public abstract string SqTypeForBinaryLengthMax { get; } + public abstract string SqlTypeForJson { get; } + public virtual string SqTypeForUnknownDotnetType => SqTypeForStringLengthMax; + protected virtual int DefaultLength { get; set; } = 255; + + public virtual bool UseIntegersForEnumTypes { get; set; } = false; + + public virtual bool TryGetDotnetTypeDescriptorMatchingFullSqlTypeName( + string fullSqlType, + out DbProviderDotnetTypeDescriptor? descriptor + ) + { + descriptor = new DbProviderDotnetTypeDescriptor(typeof(string)); + + // Prioritize any custom mappings + if (TypeMaps.TryGetValue(ProviderType, out var additionalTypeMaps)) + { + foreach (var typeMap in additionalTypeMaps) + { + if ( + typeMap.TryGetDotnetTypeDescriptorMatchingFullSqlTypeName( + fullSqlType, + out var rdt + ) + ) + { + descriptor = rdt; + return true; + } + } + } + + if ( + !TryGetProviderSqlTypeFromFullSqlTypeName(fullSqlType, out var providerSqlType) + || providerSqlType == null + ) + return false; + + // perform some detective reasoning to pinpoint a recommended type + var numbers = fullSqlType.ExtractNumbers(); + var isAutoIncrementing = providerSqlType.AutoIncrementsAutomatically; + var unicode = providerSqlType.IsUnicode; + + switch (providerSqlType.Affinity) + { + case DbProviderSqlTypeAffinity.Binary: + descriptor = new(typeof(byte[])); + break; + case DbProviderSqlTypeAffinity.Boolean: + descriptor = new( + typeof(bool), + otherSupportedTypes: + [ + typeof(short), + typeof(int), + typeof(long), + typeof(ushort), + typeof(uint), + typeof(ulong), + typeof(string) + ] + ); + break; + case DbProviderSqlTypeAffinity.DateTime: + if (providerSqlType.IsDateOnly == true) + descriptor = new( + typeof(DateOnly), + otherSupportedTypes: [typeof(DateOnly), typeof(DateTime), typeof(string)] + ); + else if (providerSqlType.IsTimeOnly == true) + descriptor = new( + typeof(TimeOnly), + otherSupportedTypes: [typeof(TimeOnly), typeof(DateTime), typeof(string)] + ); + else if (providerSqlType.IsYearOnly == true) + descriptor = new( + typeof(int), + otherSupportedTypes: + [ + typeof(short), + typeof(long), + typeof(ushort), + typeof(uint), + typeof(ulong), + typeof(string) + ] + ); + else if (providerSqlType.IncludesTimeZone == true) + descriptor = new DbProviderDotnetTypeDescriptor( + typeof(DateTimeOffset), + otherSupportedTypes: [typeof(DateTime), typeof(string)] + ); + else + descriptor = new( + typeof(DateTime), + otherSupportedTypes: [typeof(DateTimeOffset), typeof(string)] + ); + break; + case DbProviderSqlTypeAffinity.Integer: + int? intPrecision = numbers.Length > 0 ? numbers[0] : null; + if (providerSqlType.MinValue.HasValue && providerSqlType.MinValue == 0) + { + if (providerSqlType.MaxValue.HasValue) + { + if (providerSqlType.MaxValue.Value <= ushort.MaxValue) + descriptor = new( + typeof(ushort), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: + [ + typeof(short), + typeof(int), + typeof(long), + typeof(uint), + typeof(ulong), + typeof(string) + ] + ); + else if (providerSqlType.MaxValue.Value <= uint.MaxValue) + descriptor = new( + typeof(uint), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: + [ + typeof(int), + typeof(long), + typeof(ulong), + typeof(string) + ] + ); + else if (providerSqlType.MaxValue.Value <= ulong.MaxValue) + descriptor = new( + typeof(ulong), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: [typeof(long), typeof(string)] + ); + } + descriptor ??= new( + typeof(uint), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: + [ + typeof(int), + typeof(long), + typeof(ulong), + typeof(string) + ] + ); + } + if (descriptor == null) + { + if (providerSqlType.MaxValue.HasValue) + { + if (providerSqlType.MaxValue.Value <= short.MaxValue) + descriptor = new( + typeof(short), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: [typeof(int), typeof(long), typeof(string)] + ); + else if (providerSqlType.MaxValue.Value <= int.MaxValue) + descriptor = new( + typeof(int), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: [typeof(long), typeof(string)] + ); + else if (providerSqlType.MaxValue.Value <= long.MaxValue) + descriptor = new( + typeof(long), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: [typeof(string)] + ); + } + descriptor ??= new( + typeof(int), + precision: intPrecision, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: [typeof(long), typeof(string)] + ); + } + break; + case DbProviderSqlTypeAffinity.Real: + int? precision = numbers.Length > 0 ? numbers[0] : null; + int? scale = numbers.Length > 1 ? numbers[1] : null; + descriptor = new( + typeof(decimal), + precision: precision, + scale: scale, + autoIncrement: isAutoIncrementing, + otherSupportedTypes: + [ + typeof(decimal), + typeof(float), + typeof(double), + typeof(string) + ] + ); + break; + case DbProviderSqlTypeAffinity.Text: + int? length = numbers.Length > 0 ? numbers[0] : null; + if (length >= 8000) + length = int.MaxValue; + descriptor = new( + typeof(string), + length: length, + unicode: unicode, + otherSupportedTypes: [typeof(string)] + ); + break; + case DbProviderSqlTypeAffinity.Geometry: + case DbProviderSqlTypeAffinity.RangeType: + case DbProviderSqlTypeAffinity.Other: + default: + if ( + providerSqlType.Name.Contains("json", StringComparison.OrdinalIgnoreCase) + || providerSqlType.Name.Contains("xml", StringComparison.OrdinalIgnoreCase) + ) + descriptor = new( + typeof(string), + length: int.MaxValue, + unicode: unicode, + otherSupportedTypes: [typeof(string)] + ); + else + descriptor = new( + typeof(object), + otherSupportedTypes: [typeof(object), typeof(string)] + ); + break; + } + + return descriptor != null; + } + + public virtual bool TryGetProviderSqlTypeMatchingDotnetType( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ) + { + providerSqlType = null; + + // Prioritize any custom mappings + if (TypeMaps.TryGetValue(ProviderType, out var additionalTypeMaps)) + { + foreach (var typeMap in additionalTypeMaps) + { + if (typeMap.TryGetProviderSqlTypeMatchingDotnetType(descriptor, out var rdt)) + { + providerSqlType = rdt; + return true; + } + } + } + + var dotnetType = descriptor.DotnetType; + + // Enums become strings, or integers if UseIntegersForEnumTypes is true + if (dotnetType.IsEnum) + { + return TryGetProviderSqlTypeMatchingEnumType( + dotnetType, + descriptor.Length, + ref providerSqlType + ); + } + + // char becomes string(1) + if (dotnetType == typeof(char) && (descriptor.Length == null || descriptor.Length == 1)) + { + return TryGetProviderSqlTypeMatchingDotnetType( + new DbProviderDotnetTypeDescriptor(typeof(string), 1, unicode: descriptor.Unicode), + out providerSqlType + ); + } + + if (TryGetProviderSqlTypeMatchingDotnetTypeInternal(descriptor, out providerSqlType)) + return true; + + return providerSqlType != null; + } + + protected abstract bool TryGetProviderSqlTypeMatchingDotnetTypeInternal( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ); + + protected virtual bool TryGetProviderSqlTypeFromFullSqlTypeName( + string fullSqlType, + out DbProviderSqlType? providerSqlType + ) + { + // perform some detective reasoning to pinpoint a recommended type + var numbers = fullSqlType.ExtractNumbers(); + + // try to find a sql provider type match by removing the length, precision, and scale + // from the sql type name and converting it to an alpha only representation of the type + var fullSqlTypeAlpha = fullSqlType + .DiscardLengthPrecisionAndScaleFromSqlTypeName() + .ToAlpha("[]"); + + providerSqlType = ProviderSqlTypes.FirstOrDefault(t => + t.Name.DiscardLengthPrecisionAndScaleFromSqlTypeName() + .ToAlpha("[]") + .Equals(fullSqlTypeAlpha, StringComparison.OrdinalIgnoreCase) + ); + + return providerSqlType != null; + } + + protected virtual bool TryGetProviderSqlTypeMatchingEnumType( + Type dotnetType, + int? length, + ref DbProviderSqlType? providerSqlType + ) + { + if (UseIntegersForEnumTypes) + { + return TryGetProviderSqlTypeMatchingDotnetType( + new DbProviderDotnetTypeDescriptor(typeof(int), length), + out providerSqlType + ); + } + + if (length == null) + { + var maxEnumNameLength = Enum.GetNames(dotnetType).Max(m => m.Length); + var x = 64; + while (x < maxEnumNameLength) + x *= 2; + length = x; + } + + return TryGetProviderSqlTypeMatchingDotnetType( + new DbProviderDotnetTypeDescriptor(typeof(string), length), + out providerSqlType + ); + } +} diff --git a/src/DapperMatic/Providers/ProviderUtils.cs b/src/DapperMatic/Providers/DbProviderUtils.cs similarity index 97% rename from src/DapperMatic/Providers/ProviderUtils.cs rename to src/DapperMatic/Providers/DbProviderUtils.cs index 4f7d463..04b776e 100644 --- a/src/DapperMatic/Providers/ProviderUtils.cs +++ b/src/DapperMatic/Providers/DbProviderUtils.cs @@ -2,7 +2,7 @@ namespace DapperMatic.Providers; -public static partial class ProviderUtils +public static partial class DbProviderUtils { public static string GenerateCheckConstraintName(string tableName, string columnName) { @@ -54,6 +54,7 @@ string[] refColumnNames [GeneratedRegex(@"\d+(\.\d+)+")] private static partial Regex VersionPatternRegex(); + private static readonly Regex VersionPattern = VersionPatternRegex(); internal static Version ExtractVersionFromVersionString(string versionString) diff --git a/src/DapperMatic/Providers/IDbProviderTypeMap.cs b/src/DapperMatic/Providers/IDbProviderTypeMap.cs new file mode 100644 index 0000000..b555beb --- /dev/null +++ b/src/DapperMatic/Providers/IDbProviderTypeMap.cs @@ -0,0 +1,14 @@ +namespace DapperMatic.Providers; + +public interface IDbProviderTypeMap +{ + bool TryGetDotnetTypeDescriptorMatchingFullSqlTypeName( + string fullSqlType, + out DbProviderDotnetTypeDescriptor? descriptor + ); + + bool TryGetProviderSqlTypeMatchingDotnetType( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ); +} diff --git a/src/DapperMatic/Providers/IProviderTypeMap.cs b/src/DapperMatic/Providers/IProviderTypeMap.cs deleted file mode 100644 index 79e5a8b..0000000 --- a/src/DapperMatic/Providers/IProviderTypeMap.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace DapperMatic.Providers; - -public interface IProviderTypeMap -{ - bool TryGetRecommendedDotnetTypeMatchingSqlType( - string fullSqlType, - out (Type dotnetType, int? length, int? precision, int? scale, bool? isAutoIncrementing, Type[] allSupportedTypes)? recommendedDotnetType - ); - - bool TryGetRecommendedSqlTypeMatchingDotnetType( - Type dotnetType, - int? length, - int? precision, - int? scale, - bool? autoIncrement, - out ProviderSqlType? recommendedSqlType - ); -} \ No newline at end of file diff --git a/src/DapperMatic/Providers/MySql/MySqlMethods.Strings.cs b/src/DapperMatic/Providers/MySql/MySqlMethods.Strings.cs index 06e2c73..4d507d6 100644 --- a/src/DapperMatic/Providers/MySql/MySqlMethods.Strings.cs +++ b/src/DapperMatic/Providers/MySql/MySqlMethods.Strings.cs @@ -12,11 +12,6 @@ public partial class MySqlMethods protected override string SqlInlineColumnNameAndType(DxColumn column, Version dbVersion) { - if (column.DotnetType == typeof(Guid) && string.IsNullOrWhiteSpace(column.ProviderDataType)) - { - column.ProviderDataType = "varchar(36)"; - } - var nameAndType = base.SqlInlineColumnNameAndType(column, dbVersion); if ( @@ -32,11 +27,12 @@ protected override string SqlInlineColumnNameAndType(DxColumn column, Version db || dbVersion.Major == 11; // || (dbVersion.Major == 10 && dbVersion < new Version(10, 5, 25)); - if (!doNotAddUtf8Mb4) + if (!doNotAddUtf8Mb4 && column.IsUnicode) { // make it unicode by default nameAndType += " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"; } + return nameAndType; } @@ -44,19 +40,19 @@ protected override string SqlInlineColumnNameAndType(DxColumn column, Version db // MySQL DOES NOT ALLOW a named constraint in the column definition, so we HAVE to create // the primary key constraint in the table constraints section protected override string SqlInlinePrimaryKeyColumnConstraint( + DxColumn column, string constraintName, - bool isAutoIncrement, out bool useTableConstraint ) { useTableConstraint = true; - return isAutoIncrement ? "AUTO_INCREMENT" : ""; + return column.IsAutoIncrement ? "AUTO_INCREMENT" : ""; // the following code doesn't work because MySQL doesn't allow named constraints in the column definition - // return $"CONSTRAINT {NormalizeName(constraintName)} {(isAutoIncrement ? $"{SqlInlinePrimaryKeyAutoIncrementColumnConstraint()} " : "")}PRIMARY KEY".Trim(); + // return $"CONSTRAINT {NormalizeName(constraintName)} {(column.IsAutoIncrement ? $"{SqlInlinePrimaryKeyAutoIncrementColumnConstraint(column)} " : "")}PRIMARY KEY".Trim(); } - protected override string SqlInlinePrimaryKeyAutoIncrementColumnConstraint() + protected override string SqlInlinePrimaryKeyAutoIncrementColumnConstraint(DxColumn column) { return "AUTO_INCREMENT"; } diff --git a/src/DapperMatic/Providers/MySql/MySqlMethods.Tables.cs b/src/DapperMatic/Providers/MySql/MySqlMethods.Tables.cs index 4d29eb5..8a3a8c1 100644 --- a/src/DapperMatic/Providers/MySql/MySqlMethods.Tables.cs +++ b/src/DapperMatic/Providers/MySql/MySqlMethods.Tables.cs @@ -167,7 +167,7 @@ string columns_desc_csv DefaultSchema, c.table_name, c.column_name, - ProviderUtils.GenerateDefaultConstraintName(c.table_name, c.column_name), + DbProviderUtils.GenerateDefaultConstraintName(c.table_name, c.column_name), c.column_default.Trim('(', ')') ); }) @@ -182,7 +182,7 @@ string columns_desc_csv return new DxPrimaryKeyConstraint( DefaultSchema, t.table_name, - ProviderUtils.GeneratePrimaryKeyConstraintName(t.table_name, columnNames), + DbProviderUtils.GeneratePrimaryKeyConstraintName(t.table_name, columnNames), columnNames .Select( (c, i) => @@ -457,16 +457,17 @@ string check_expression ) ?.i; - var (dotnetType, _, _, _, _, _) = GetDotnetTypeFromSqlType( - tableColumn.data_type_complete - ); + var dotnetTypeDescriptor = GetDotnetTypeFromSqlType(tableColumn.data_type_complete); var column = new DxColumn( tableColumn.schema_name, tableColumn.table_name, tableColumn.column_name, - dotnetType, - tableColumn.data_type_complete, + dotnetTypeDescriptor.DotnetType, + new Dictionary + { + { ProviderType, tableColumn.data_type_complete } + }, tableColumn.max_length.HasValue ? ( tableColumn.max_length.Value > int.MaxValue diff --git a/src/DapperMatic/Providers/MySql/MySqlMethods.cs b/src/DapperMatic/Providers/MySql/MySqlMethods.cs index 8733324..bfeb0a0 100644 --- a/src/DapperMatic/Providers/MySql/MySqlMethods.cs +++ b/src/DapperMatic/Providers/MySql/MySqlMethods.cs @@ -8,7 +8,7 @@ public partial class MySqlMethods : DatabaseMethodsBase, IDatabaseMethods { public override DbProviderType ProviderType => DbProviderType.MySql; - public override IProviderTypeMap ProviderTypeMap => MySqlProviderTypeMap.Instance.Value; + public override IDbProviderTypeMap ProviderTypeMap => MySqlProviderTypeMap.Instance.Value; protected override string DefaultSchema => ""; @@ -21,7 +21,7 @@ public override async Task SupportsCheckConstraintsAsync( var versionStr = await ExecuteScalarAsync(db, "SELECT VERSION()", tx: tx).ConfigureAwait(false) ?? ""; - var version = ProviderUtils.ExtractVersionFromVersionString(versionStr); + var version = DbProviderUtils.ExtractVersionFromVersionString(versionStr); return ( versionStr.Contains("MariaDB", StringComparison.OrdinalIgnoreCase) && version > new Version(10, 2, 1) @@ -50,7 +50,7 @@ public override async Task GetDatabaseVersionAsync( var sql = @"SELECT VERSION()"; var versionString = await ExecuteScalarAsync(db, sql, tx: tx).ConfigureAwait(false) ?? ""; - return ProviderUtils.ExtractVersionFromVersionString(versionString); + return DbProviderUtils.ExtractVersionFromVersionString(versionString); } public override char[] QuoteChars => ['`']; diff --git a/src/DapperMatic/Providers/MySql/MySqlProviderTypeMap.cs b/src/DapperMatic/Providers/MySql/MySqlProviderTypeMap.cs index 7821124..80fd4df 100644 --- a/src/DapperMatic/Providers/MySql/MySqlProviderTypeMap.cs +++ b/src/DapperMatic/Providers/MySql/MySqlProviderTypeMap.cs @@ -1,112 +1,435 @@ +using System.Collections; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Text.Json; +using System.Text.Json.Nodes; + namespace DapperMatic.Providers.MySql; -public class MySqlProviderTypeMap : ProviderTypeMapBase +public sealed class MySqlProviderTypeMap : DbProviderTypeMapBase { - internal static readonly Lazy Instance = - new(() => new MySqlProviderTypeMap()); - - private MySqlProviderTypeMap() : base() - { - } - - protected override DbProviderType ProviderType => DbProviderType.MySql; - - /// - /// IMPORTANT!! The order within an affinity group matters, as the first possible match will be used as the recommended sql type for a dotnet type - /// - protected override ProviderSqlType[] ProviderSqlTypes => - [ - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_tinyint, formatWithPrecision: "tinyint({0})", - defaultPrecision: 4, canUseToAutoIncrement: true, minValue: -128, maxValue: 128), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_tinyint_unsigned, - formatWithPrecision: "tinyint({0}) unsigned", defaultPrecision: 4, canUseToAutoIncrement: true, minValue: 0, - maxValue: 255), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_smallint, formatWithPrecision: "smallint({0})", - defaultPrecision: 5, canUseToAutoIncrement: true, minValue: -32768, maxValue: 32767), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_smallint_unsigned, - formatWithPrecision: "smallint({0}) unsigned", defaultPrecision: 5, canUseToAutoIncrement: true, - minValue: 0, maxValue: 65535), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_mediumint, formatWithPrecision: "mediumint({0})", - defaultPrecision: 7, canUseToAutoIncrement: true, minValue: -8388608, maxValue: 8388607), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_mediumint_unsigned, - formatWithPrecision: "mediumint({0}) unsigned", defaultPrecision: 7, canUseToAutoIncrement: true, - minValue: 0, maxValue: 16777215), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_integer, formatWithPrecision: "integer({0})", - defaultPrecision: 11, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_integer_unsigned, - formatWithPrecision: "integer({0}) unsigned", defaultPrecision: 11, canUseToAutoIncrement: true, - minValue: 0, maxValue: 4294967295), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_int, aliasOf: "integer", formatWithPrecision: "int({0})", - defaultPrecision: 11, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_int_unsigned, formatWithPrecision: "int({0}) unsigned", - defaultPrecision: 11, canUseToAutoIncrement: true, minValue: 0, maxValue: 4294967295), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_bigint, formatWithPrecision: "bigint({0})", - defaultPrecision: 19, canUseToAutoIncrement: true, minValue: -Math.Pow(2, 63), - maxValue: Math.Pow(2, 63) - 1), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_bigint_unsigned, - formatWithPrecision: "bigint({0}) unsigned", defaultPrecision: 19, canUseToAutoIncrement: true, minValue: 0, - maxValue: Math.Pow(2, 64) - 1), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_serial, aliasOf: "bigint unsigned", - canUseToAutoIncrement: true, autoIncrementsAutomatically: true, minValue: 0, maxValue: Math.Pow(2, 64) - 1), - new(ProviderSqlTypeAffinity.Integer, MySqlTypes.sql_bit, formatWithPrecision: "bit({0})", defaultPrecision: 1, - minValue: 0, maxValue: long.MaxValue), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_decimal, formatWithPrecision: "decimal({0})", - formatWithPrecisionAndScale: "decimal({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_dec, aliasOf: "decimal", formatWithPrecision: "dec({0})", - formatWithPrecisionAndScale: "dec({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_numeric, formatWithPrecision: "numeric({0})", - formatWithPrecisionAndScale: "numeric({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_fixed, aliasOf: "decimal", formatWithPrecision: "fixed({0})", - formatWithPrecisionAndScale: "fixed({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_float, formatWithPrecision: "float({0})", - formatWithPrecisionAndScale: "float({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_real, aliasOf: "double", - formatWithPrecisionAndScale: "real({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_double_precision, aliasOf: "double", - formatWithPrecisionAndScale: "double precision({0},{1})", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_double_precision_unsigned, aliasOf: "double unsigned", - formatWithPrecisionAndScale: "double precision({0},{1}) unsigned", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_double, formatWithPrecisionAndScale: "double({0},{1})", - defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, MySqlTypes.sql_double_unsigned, - formatWithPrecisionAndScale: "double({0},{1}) unsigned", defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Boolean, MySqlTypes.sql_bool, aliasOf: "tinyint(1)"), - new(ProviderSqlTypeAffinity.Boolean, MySqlTypes.sql_boolean, aliasOf: "tinyint(1)"), - new(ProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_datetime), - new(ProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_timestamp), - new(ProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_time, formatWithPrecision: "time({0})", - defaultPrecision: 6, isTimeOnly: true), - new(ProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_date, isDateOnly: true), - new(ProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_year, isYearOnly: true), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_char, formatWithLength: "char({0})", defaultLength: 255, - isFixedLength: true), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_varchar, formatWithLength: "varchar({0})", - defaultLength: 8000), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_long_varchar, aliasOf: "mediumtext"), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_tinytext), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_text, isMaxStringLengthType: true), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_mediumtext), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_longtext), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_enum), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_set), - new(ProviderSqlTypeAffinity.Text, MySqlTypes.sql_json), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_blob), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_tinyblob), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_mediumblob), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_longblob), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_binary, formatWithLength: "binary({0})", defaultLength: 255, - isFixedLength: true), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_varbinary, formatWithLength: "varbinary({0})", - defaultLength: 8000), - new(ProviderSqlTypeAffinity.Binary, MySqlTypes.sql_long_varbinary, aliasOf: "mediumblob"), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_geometry), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_point), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_linestring), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_polygon), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_multipoint), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_multilinestring), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_multipolygon), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_geomcollection), - new(ProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_geometrycollection, aliasOf: "geomcollection") - ]; -} \ No newline at end of file + internal static readonly Lazy Instance = + new(() => new MySqlProviderTypeMap()); + + private MySqlProviderTypeMap() + : base() { } + + protected override DbProviderType ProviderType => DbProviderType.MySql; + + public override string SqTypeForStringLengthMax => "text(65535)"; + + public override string SqTypeForBinaryLengthMax => "blob(65535)"; + + public override string SqlTypeForJson => "text(65535)"; + + /// + /// IMPORTANT!! The order within an affinity group matters, as the first possible match will be used as the recommended sql type for a dotnet type + /// + protected override DbProviderSqlType[] ProviderSqlTypes => + [ + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_tinyint, + formatWithPrecision: "tinyint({0})", + defaultPrecision: 4, + canUseToAutoIncrement: true, + minValue: -128, + maxValue: 128 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_tinyint_unsigned, + formatWithPrecision: "tinyint({0}) unsigned", + defaultPrecision: 4, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: 255 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_smallint, + formatWithPrecision: "smallint({0})", + defaultPrecision: 5, + canUseToAutoIncrement: true, + minValue: -32768, + maxValue: 32767 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_smallint_unsigned, + formatWithPrecision: "smallint({0}) unsigned", + defaultPrecision: 5, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: 65535 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_mediumint, + formatWithPrecision: "mediumint({0})", + defaultPrecision: 7, + canUseToAutoIncrement: true, + minValue: -8388608, + maxValue: 8388607 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_mediumint_unsigned, + formatWithPrecision: "mediumint({0}) unsigned", + defaultPrecision: 7, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: 16777215 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_integer, + formatWithPrecision: "integer({0})", + defaultPrecision: 11, + canUseToAutoIncrement: true, + minValue: -2147483648, + maxValue: 2147483647 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_integer_unsigned, + formatWithPrecision: "integer({0}) unsigned", + defaultPrecision: 11, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: 4294967295 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_int, + aliasOf: "integer", + formatWithPrecision: "int({0})", + defaultPrecision: 11, + canUseToAutoIncrement: true, + minValue: -2147483648, + maxValue: 2147483647 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_int_unsigned, + formatWithPrecision: "int({0}) unsigned", + defaultPrecision: 11, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: 4294967295 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_bigint, + formatWithPrecision: "bigint({0})", + defaultPrecision: 19, + canUseToAutoIncrement: true, + minValue: -Math.Pow(2, 63), + maxValue: Math.Pow(2, 63) - 1 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_bigint_unsigned, + formatWithPrecision: "bigint({0}) unsigned", + defaultPrecision: 19, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: Math.Pow(2, 64) - 1 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_serial, + aliasOf: "bigint unsigned", + canUseToAutoIncrement: true, + autoIncrementsAutomatically: true, + minValue: 0, + maxValue: Math.Pow(2, 64) - 1 + ), + new( + DbProviderSqlTypeAffinity.Integer, + MySqlTypes.sql_bit, + formatWithPrecision: "bit({0})", + defaultPrecision: 1, + minValue: 0, + maxValue: long.MaxValue + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_decimal, + formatWithPrecision: "decimal({0})", + formatWithPrecisionAndScale: "decimal({0},{1})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_dec, + aliasOf: "decimal", + formatWithPrecision: "dec({0})", + formatWithPrecisionAndScale: "dec({0},{1})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_numeric, + formatWithPrecision: "numeric({0})", + formatWithPrecisionAndScale: "numeric({0},{1})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_fixed, + aliasOf: "decimal", + formatWithPrecision: "fixed({0})", + formatWithPrecisionAndScale: "fixed({0},{1})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_float + // formatWithPrecision: "float({0})", + // formatWithPrecisionAndScale: "float({0},{1})", + // defaultPrecision: 12, + // defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_real + // aliasOf: "double", + // formatWithPrecisionAndScale: "real({0},{1})", + // defaultPrecision: 12, + // defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_double_precision + // aliasOf: "double", + // formatWithPrecisionAndScale: "double precision({0},{1})", + // defaultPrecision: 12, + // defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_double_precision_unsigned, + aliasOf: "double unsigned" + // formatWithPrecisionAndScale: "double precision({0},{1}) unsigned", + // defaultPrecision: 12, + // defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_double + // formatWithPrecisionAndScale: "double({0},{1})", + // defaultPrecision: 12, + // defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + MySqlTypes.sql_double_unsigned + // formatWithPrecisionAndScale: "double({0},{1}) unsigned", + // defaultPrecision: 12, + // defaultScale: 2 + ), + new(DbProviderSqlTypeAffinity.Boolean, MySqlTypes.sql_bool, aliasOf: "tinyint(1)"), + new(DbProviderSqlTypeAffinity.Boolean, MySqlTypes.sql_boolean, aliasOf: "tinyint(1)"), + new(DbProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_datetime), + new(DbProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_timestamp), + new( + DbProviderSqlTypeAffinity.DateTime, + MySqlTypes.sql_time, + formatWithPrecision: "time({0})", + defaultPrecision: 6, + isTimeOnly: true + ), + new(DbProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_date, isDateOnly: true), + new(DbProviderSqlTypeAffinity.DateTime, MySqlTypes.sql_year, isYearOnly: true), + new( + DbProviderSqlTypeAffinity.Text, + MySqlTypes.sql_char, + formatWithLength: "char({0})", + defaultLength: DefaultLength, + isFixedLength: true + ), + new( + DbProviderSqlTypeAffinity.Text, + MySqlTypes.sql_varchar, + formatWithLength: "varchar({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + MySqlTypes.sql_text, + formatWithLength: "text({0})", + defaultLength: 65535 + ), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_long_varchar, aliasOf: "mediumtext"), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_tinytext), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_mediumtext), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_longtext), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_enum), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_set), + new(DbProviderSqlTypeAffinity.Text, MySqlTypes.sql_json), + new( + DbProviderSqlTypeAffinity.Binary, + MySqlTypes.sql_blob, + formatWithLength: "blob({0})", + defaultLength: 65535 + ), + new(DbProviderSqlTypeAffinity.Binary, MySqlTypes.sql_tinyblob), + new(DbProviderSqlTypeAffinity.Binary, MySqlTypes.sql_mediumblob), + new(DbProviderSqlTypeAffinity.Binary, MySqlTypes.sql_longblob), + new( + DbProviderSqlTypeAffinity.Binary, + MySqlTypes.sql_varbinary, + formatWithLength: "varbinary({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Binary, + MySqlTypes.sql_binary, + formatWithLength: "binary({0})", + defaultLength: DefaultLength, + isFixedLength: true + ), + new( + DbProviderSqlTypeAffinity.Binary, + MySqlTypes.sql_long_varbinary, + aliasOf: "mediumblob" + ), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_geometry), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_point), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_linestring), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_polygon), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_multipoint), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_multilinestring), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_multipolygon), + new(DbProviderSqlTypeAffinity.Geometry, MySqlTypes.sql_geomcollection), + new( + DbProviderSqlTypeAffinity.Geometry, + MySqlTypes.sql_geometrycollection, + aliasOf: "geomcollection" + ) + ]; + + protected override bool TryGetProviderSqlTypeMatchingDotnetTypeInternal( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ) + { + providerSqlType = null; + + var dotnetType = descriptor.DotnetType; + + // handle well-known types first + providerSqlType = dotnetType.IsGenericType + ? null + : dotnetType switch + { + Type t when t == typeof(bool) => ProviderSqlTypeLookup[MySqlTypes.sql_boolean], + Type t when t == typeof(byte) + => ProviderSqlTypeLookup[MySqlTypes.sql_smallint_unsigned], + Type t when t == typeof(ReadOnlyMemory) + => ProviderSqlTypeLookup[MySqlTypes.sql_smallint], + Type t when t == typeof(sbyte) => ProviderSqlTypeLookup[MySqlTypes.sql_smallint], + Type t when t == typeof(short) => ProviderSqlTypeLookup[MySqlTypes.sql_smallint], + Type t when t == typeof(ushort) + => ProviderSqlTypeLookup[MySqlTypes.sql_smallint_unsigned], + Type t when t == typeof(int) => ProviderSqlTypeLookup[MySqlTypes.sql_int], + Type t when t == typeof(uint) => ProviderSqlTypeLookup[MySqlTypes.sql_int_unsigned], + Type t when t == typeof(long) => ProviderSqlTypeLookup[MySqlTypes.sql_bigint], + Type t when t == typeof(ulong) + => ProviderSqlTypeLookup[MySqlTypes.sql_bigint_unsigned], + Type t when t == typeof(float) => ProviderSqlTypeLookup[MySqlTypes.sql_float], + Type t when t == typeof(double) => ProviderSqlTypeLookup[MySqlTypes.sql_double], + Type t when t == typeof(decimal) => ProviderSqlTypeLookup[MySqlTypes.sql_decimal], + Type t when t == typeof(char) => ProviderSqlTypeLookup[MySqlTypes.sql_varchar], + Type t when t == typeof(string) + => descriptor.Length.GetValueOrDefault(255) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(char[]) + => descriptor.Length.GetValueOrDefault(255) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(ReadOnlyMemory[]) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(Stream) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(TextReader) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(byte[]) => ProviderSqlTypeLookup[MySqlTypes.sql_blob], + Type t when t == typeof(object) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(object[]) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[MySqlTypes.sql_varchar] + : ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t when t == typeof(Guid) => ProviderSqlTypeLookup[MySqlTypes.sql_varchar], + Type t when t == typeof(DateTime) => ProviderSqlTypeLookup[MySqlTypes.sql_datetime], + Type t when t == typeof(DateTimeOffset) + => ProviderSqlTypeLookup[MySqlTypes.sql_timestamp], + Type t when t == typeof(TimeSpan) => ProviderSqlTypeLookup[MySqlTypes.sql_bigint], + Type t when t == typeof(DateOnly) => ProviderSqlTypeLookup[MySqlTypes.sql_date], + Type t when t == typeof(TimeOnly) => ProviderSqlTypeLookup[MySqlTypes.sql_time], + Type t when t == typeof(BitArray) || t == typeof(BitVector32) + => ProviderSqlTypeLookup[MySqlTypes.sql_varbinary], + Type t + when t == typeof(ImmutableDictionary) + || t == typeof(Dictionary) + || t == typeof(IDictionary) + => ProviderSqlTypeLookup[MySqlTypes.sql_text], + Type t + when t == typeof(JsonNode) + || t == typeof(JsonObject) + || t == typeof(JsonArray) + || t == typeof(JsonValue) + || t == typeof(JsonDocument) + || t == typeof(JsonElement) + => ProviderSqlTypeLookup[MySqlTypes.sql_text], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle generic types + providerSqlType = !dotnetType.IsGenericType + ? null + : dotnetType.GetGenericTypeDefinition() switch + { + Type t + when t == typeof(Dictionary<,>) + || t == typeof(IDictionary<,>) + || t == typeof(List<>) + || t == typeof(IList<>) + || t == typeof(Collection<>) + || t == typeof(ICollection<>) + || t == typeof(IEnumerable<>) + => ProviderSqlTypeLookup[MySqlTypes.sql_text], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle POCO type + if (dotnetType.IsClass || dotnetType.IsInterface) + { + providerSqlType = ProviderSqlTypeLookup[MySqlTypes.sql_text]; + } + + return providerSqlType != null; + } +} diff --git a/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Strings.cs b/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Strings.cs index f11e3e8..b1e1628 100644 --- a/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Strings.cs +++ b/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Strings.cs @@ -33,20 +33,31 @@ protected override string SqlDropSchema(string schemaName) protected override string SqlInlineColumnNullable(DxColumn column) { + // serial columns are implicitly NOT NULL if ( column.IsNullable - && (column.ProviderDataType ?? "").Contains( + && (column.GetProviderDataType(ProviderType) ?? "").Contains( "serial", StringComparison.OrdinalIgnoreCase ) ) return ""; - return column.IsNullable ? " NULL" : " NOT NULL"; + return column.IsNullable && !column.IsUnique && !column.IsPrimaryKey + ? " NULL" + : " NOT NULL"; } - protected override string SqlInlinePrimaryKeyAutoIncrementColumnConstraint() + protected override string SqlInlinePrimaryKeyAutoIncrementColumnConstraint(DxColumn column) { + if ( + (column.GetProviderDataType(ProviderType) ?? "").Contains( + "serial", + StringComparison.OrdinalIgnoreCase + ) + ) + return string.Empty; + return "GENERATED BY DEFAULT AS IDENTITY"; } diff --git a/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Tables.cs b/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Tables.cs index 1ae2f8d..7781459 100644 --- a/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Tables.cs +++ b/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.Tables.cs @@ -399,22 +399,24 @@ int column_ordinal ) ?.i; - var (dotnetType, length, precision, scale, autoIncrementing, otherSupportedTypes) = - GetDotnetTypeFromSqlType( - tableColumn.data_type.Length < tableColumn.data_type_ext.Length - ? tableColumn.data_type_ext - : tableColumn.data_type - ); + var dotnetTypeDescriptor = GetDotnetTypeFromSqlType( + tableColumn.data_type.Length < tableColumn.data_type_ext.Length + ? tableColumn.data_type_ext + : tableColumn.data_type + ); var column = new DxColumn( tableColumn.schema_name, tableColumn.table_name, tableColumn.column_name, - dotnetType, - tableColumn.data_type, - length, - precision, - scale, + dotnetTypeDescriptor.DotnetType, + new Dictionary + { + { ProviderType, tableColumn.data_type } + }, + dotnetTypeDescriptor.Length, + dotnetTypeDescriptor.Precision, + dotnetTypeDescriptor.Scale, tableCheckConstraints .FirstOrDefault(c => !string.IsNullOrWhiteSpace(c.ColumnName) diff --git a/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.cs b/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.cs index 82cb6bf..b4f6139 100644 --- a/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.cs +++ b/src/DapperMatic/Providers/PostgreSql/PostgreSqlMethods.cs @@ -8,7 +8,7 @@ public partial class PostgreSqlMethods : DatabaseMethodsBase, IDatabaseMethods { public override DbProviderType ProviderType => DbProviderType.PostgreSql; - public override IProviderTypeMap ProviderTypeMap => PostgreSqlProviderTypeMap.Instance.Value; + public override IDbProviderTypeMap ProviderTypeMap => PostgreSqlProviderTypeMap.Instance.Value; private static string _defaultSchema = "public"; protected override string DefaultSchema => _defaultSchema; @@ -39,7 +39,7 @@ public override async Task GetDatabaseVersionAsync( const string sql = "SELECT VERSION()"; var versionString = await ExecuteScalarAsync(db, sql, tx: tx).ConfigureAwait(false) ?? ""; - return ProviderUtils.ExtractVersionFromVersionString(versionString); + return DbProviderUtils.ExtractVersionFromVersionString(versionString); } public override char[] QuoteChars => ['"']; diff --git a/src/DapperMatic/Providers/PostgreSql/PostgreSqlProviderTypeMap.cs b/src/DapperMatic/Providers/PostgreSql/PostgreSqlProviderTypeMap.cs index b4d9a09..02755c3 100644 --- a/src/DapperMatic/Providers/PostgreSql/PostgreSqlProviderTypeMap.cs +++ b/src/DapperMatic/Providers/PostgreSql/PostgreSqlProviderTypeMap.cs @@ -1,8 +1,15 @@ +using System.Collections; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Text.Json; +using System.Text.Json.Nodes; + namespace DapperMatic.Providers.PostgreSql; // https://www.npgsql.org/doc/types/basic.html#read-mappings // https://www.npgsql.org/doc/types/basic.html#write-mappings -public sealed class PostgreSqlProviderTypeMap : ProviderTypeMapBase +public sealed class PostgreSqlProviderTypeMap : DbProviderTypeMapBase { internal static readonly Lazy Instance = new(() => new PostgreSqlProviderTypeMap()); @@ -12,62 +19,68 @@ private PostgreSqlProviderTypeMap() protected override DbProviderType ProviderType => DbProviderType.PostgreSql; + public override string SqTypeForStringLengthMax => "text"; + + public override string SqTypeForBinaryLengthMax => "bytea"; + + public override string SqlTypeForJson => "jsonb"; + /// /// IMPORTANT!! The order within an affinity group matters, as the first possible match will be used as the recommended sql type for a dotnet type /// - protected override ProviderSqlType[] ProviderSqlTypes => + protected override DbProviderSqlType[] ProviderSqlTypes => [ new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_smallint, canUseToAutoIncrement: true, minValue: -32768, maxValue: 32767 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_int2, canUseToAutoIncrement: true, minValue: -32768, maxValue: 32767 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_integer, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_int, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_int4, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_int8, canUseToAutoIncrement: true, minValue: -9223372036854775808, maxValue: 9223372036854775807 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_bigint, canUseToAutoIncrement: true, minValue: -9223372036854775808, maxValue: 9223372036854775807 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_smallserial, canUseToAutoIncrement: true, autoIncrementsAutomatically: true, @@ -75,7 +88,7 @@ private PostgreSqlProviderTypeMap() maxValue: 32767 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_serial2, canUseToAutoIncrement: true, autoIncrementsAutomatically: true, @@ -83,7 +96,7 @@ private PostgreSqlProviderTypeMap() maxValue: 32767 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_serial, canUseToAutoIncrement: true, autoIncrementsAutomatically: true, @@ -91,7 +104,7 @@ private PostgreSqlProviderTypeMap() maxValue: 2147483647 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_serial4, canUseToAutoIncrement: true, autoIncrementsAutomatically: true, @@ -99,7 +112,7 @@ private PostgreSqlProviderTypeMap() maxValue: 2147483647 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_bigserial, canUseToAutoIncrement: true, autoIncrementsAutomatically: true, @@ -107,7 +120,7 @@ private PostgreSqlProviderTypeMap() maxValue: 9223372036854775807 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, PostgreSqlTypes.sql_serial8, canUseToAutoIncrement: true, autoIncrementsAutomatically: true, @@ -115,31 +128,31 @@ private PostgreSqlProviderTypeMap() maxValue: 9223372036854775807 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_real, minValue: float.MinValue, maxValue: float.MaxValue ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_double_precision, minValue: double.MinValue, maxValue: double.MaxValue ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_float4, minValue: float.MinValue, maxValue: float.MaxValue ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_float8, minValue: double.MinValue, maxValue: double.MaxValue ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_money, formatWithPrecision: "money({0})", defaultPrecision: 19, @@ -147,7 +160,7 @@ private PostgreSqlProviderTypeMap() maxValue: 92233720368547758.07 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_numeric, formatWithPrecision: "numeric({0})", formatWithPrecisionAndScale: "numeric({0},{1})", @@ -155,7 +168,7 @@ private PostgreSqlProviderTypeMap() defaultScale: 2 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, PostgreSqlTypes.sql_decimal, formatWithPrecision: "decimal({0})", formatWithPrecisionAndScale: "decimal({0},{1})", @@ -163,169 +176,469 @@ private PostgreSqlProviderTypeMap() defaultScale: 2 ), new( - ProviderSqlTypeAffinity.Boolean, + DbProviderSqlTypeAffinity.Boolean, PostgreSqlTypes.sql_bool, canUseToAutoIncrement: false ), - new(ProviderSqlTypeAffinity.Boolean, PostgreSqlTypes.sql_boolean), - new(ProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_date, isDateOnly: true), - new(ProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_interval), + new(DbProviderSqlTypeAffinity.Boolean, PostgreSqlTypes.sql_boolean), + new(DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_date, isDateOnly: true), + new(DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_interval), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_time_without_timezone, formatWithPrecision: "time({0}) without timezone", defaultPrecision: 6 ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_time, formatWithPrecision: "time({0})", defaultPrecision: 6, isTimeOnly: true ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_time_with_time_zone, formatWithPrecision: "time({0}) with time zone", defaultPrecision: 6 ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_timetz, formatWithPrecision: "timetz({0})", defaultPrecision: 6, isTimeOnly: true ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_timestamp_without_time_zone, formatWithPrecision: "timestamp({0}) without time zone", defaultPrecision: 6 ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_timestamp, formatWithPrecision: "timestamp({0})", defaultPrecision: 6 ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_timestamp_with_time_zone, formatWithPrecision: "timestamp({0}) with time zone", defaultPrecision: 6 ), new( - ProviderSqlTypeAffinity.DateTime, + DbProviderSqlTypeAffinity.DateTime, PostgreSqlTypes.sql_timestamptz, formatWithPrecision: "timestamptz({0})", defaultPrecision: 6 ), new( - ProviderSqlTypeAffinity.Text, - PostgreSqlTypes.sql_bit, - formatWithPrecision: "bit({0})", - defaultPrecision: 1, - minValue: 0, - maxValue: 1 - ), - new( - ProviderSqlTypeAffinity.Text, - PostgreSqlTypes.sql_bit_varying, - formatWithPrecision: "bit varying({0})", - defaultPrecision: 63 - ), - new( - ProviderSqlTypeAffinity.Text, - PostgreSqlTypes.sql_varbit, - formatWithPrecision: "varbit({0})", - defaultPrecision: 63 + DbProviderSqlTypeAffinity.Text, + PostgreSqlTypes.sql_varchar, + formatWithLength: "varchar({0})", + defaultLength: DefaultLength ), new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_character_varying, formatWithLength: "character varying({0})", - defaultLength: 255 + defaultLength: DefaultLength ), new( - ProviderSqlTypeAffinity.Text, - PostgreSqlTypes.sql_varchar, - formatWithLength: "varchar({0})", - defaultLength: 255 - ), - new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_character, formatWithLength: "character({0})", - defaultLength: 1 + defaultLength: DefaultLength ), new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_char, formatWithLength: "char({0})", - defaultLength: 1 + defaultLength: DefaultLength ), new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_bpchar, formatWithLength: "bpchar({0})", - defaultLength: 1 - ), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_text), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_name), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_uuid, isGuidOnly: true), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_json), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_jsonb), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_jsonpath), - new(ProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_xml), - new(ProviderSqlTypeAffinity.Binary, PostgreSqlTypes.sql_bytea), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_box), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_circle), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_geography), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_geometry), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_line), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_lseg), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_path), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_point), - new(ProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_polygon), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_datemultirange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_daterange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int4multirange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int4range), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int8multirange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int8range), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_nummultirange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_numrange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tsmultirange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tsrange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tstzmultirange), - new(ProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tstzrange), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_cidr), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_citext), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_hstore), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_inet), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_int2vector), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_lquery), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_ltree), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_ltxtquery), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_macaddr), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_macaddr8), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_oid), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_oidvector), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_pg_lsn), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_pg_snapshot), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_refcursor), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regclass), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regcollation), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regconfig), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regdictionary), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regnamespace), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regrole), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regtype), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_tid), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_tsquery), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_tsvector), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_txid_snapshot), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_xid), - new(ProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_xid8), + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + PostgreSqlTypes.sql_bit, + formatWithPrecision: "bit({0})", + defaultPrecision: 1, + minValue: 0, + maxValue: 1 + ), + new( + DbProviderSqlTypeAffinity.Text, + PostgreSqlTypes.sql_bit_varying, + formatWithPrecision: "bit varying({0})", + defaultPrecision: 63 + ), + new( + DbProviderSqlTypeAffinity.Text, + PostgreSqlTypes.sql_varbit, + formatWithPrecision: "varbit({0})", + defaultPrecision: 63 + ), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_text), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_name), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_uuid, isGuidOnly: true), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_json), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_jsonb), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_jsonpath), + new(DbProviderSqlTypeAffinity.Text, PostgreSqlTypes.sql_xml), + new(DbProviderSqlTypeAffinity.Binary, PostgreSqlTypes.sql_bytea), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_box), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_circle), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_geography), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_geometry), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_line), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_lseg), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_path), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_point), + new(DbProviderSqlTypeAffinity.Geometry, PostgreSqlTypes.sql_polygon), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_datemultirange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_daterange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int4multirange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int4range), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int8multirange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_int8range), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_nummultirange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_numrange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tsmultirange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tsrange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tstzmultirange), + new(DbProviderSqlTypeAffinity.RangeType, PostgreSqlTypes.sql_tstzrange), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_cidr), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_citext), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_hstore), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_inet), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_int2vector), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_lquery), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_ltree), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_ltxtquery), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_macaddr), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_macaddr8), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_oid), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_oidvector), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_pg_lsn), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_pg_snapshot), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_refcursor), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regclass), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regcollation), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regconfig), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regdictionary), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regnamespace), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regrole), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_regtype), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_tid), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_tsquery), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_tsvector), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_txid_snapshot), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_xid), + new(DbProviderSqlTypeAffinity.Other, PostgreSqlTypes.sql_xid8), ]; + + protected override bool TryGetProviderSqlTypeMatchingDotnetTypeInternal( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ) + { + providerSqlType = null; + + var dotnetType = descriptor.DotnetType; + + // handle well-known types first + providerSqlType = dotnetType.IsGenericType + ? null + : dotnetType switch + { + Type t when t == typeof(bool) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_boolean], + Type t when t == typeof(byte) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + Type t when t == typeof(ReadOnlyMemory) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + Type t when t == typeof(sbyte) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + // it's no longer recommended to use SERIAL auto-incrementing columns + // Type t when t == typeof(short) + // => descriptor.AutoIncrement.GetValueOrDefault(false) + // ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_serial2] + // : ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + // Type t when t == typeof(ushort) + // => descriptor.AutoIncrement.GetValueOrDefault(false) + // ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_serial2] + // : ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + // Type t when t == typeof(int) + // => descriptor.AutoIncrement.GetValueOrDefault(false) + // ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_serial4] + // : ProviderSqlTypeLookup[PostgreSqlTypes.sql_int4], + // Type t when t == typeof(uint) + // => descriptor.AutoIncrement.GetValueOrDefault(false) + // ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_serial4] + // : ProviderSqlTypeLookup[PostgreSqlTypes.sql_int4], + // Type t when t == typeof(long) + // => descriptor.AutoIncrement.GetValueOrDefault(false) + // ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_serial8] + // : ProviderSqlTypeLookup[PostgreSqlTypes.sql_int8], + // Type t when t == typeof(ulong) + // => descriptor.AutoIncrement.GetValueOrDefault(false) + // ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_serial8] + // : ProviderSqlTypeLookup[PostgreSqlTypes.sql_int8], + Type t when t == typeof(short) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + Type t when t == typeof(ushort) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int2], + Type t when t == typeof(int) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int4], + Type t when t == typeof(uint) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int4], + Type t when t == typeof(long) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int8], + Type t when t == typeof(ulong) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int8], + Type t when t == typeof(float) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_float4], + Type t when t == typeof(double) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_float8], + Type t when t == typeof(decimal) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_numeric], + Type t when t == typeof(char) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar], + Type t when t == typeof(string) + => descriptor.Length.GetValueOrDefault(255) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(char[]) + => descriptor.Length.GetValueOrDefault(255) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(ReadOnlyMemory[]) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(Stream) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(TextReader) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(byte[]) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_bytea], + Type t when t == typeof(object) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(object[]) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[PostgreSqlTypes.sql_varchar] + : ProviderSqlTypeLookup[PostgreSqlTypes.sql_text], + Type t when t == typeof(Guid) => ProviderSqlTypeLookup[PostgreSqlTypes.sql_uuid], + Type t when t == typeof(DateTime) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_timestamp], + Type t when t == typeof(DateTimeOffset) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_timestamptz], + Type t when t == typeof(TimeSpan) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_interval], + Type t when t == typeof(DateOnly) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_date], + Type t when t == typeof(TimeOnly) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_time], + Type t when t == typeof(BitArray) || t == typeof(BitVector32) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_varbit], + Type t + when t == typeof(ImmutableDictionary) + || t == typeof(Dictionary) + || t == typeof(IDictionary) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_hstore], + Type t + when t == typeof(JsonNode) + || t == typeof(JsonObject) + || t == typeof(JsonArray) + || t == typeof(JsonValue) + || t == typeof(JsonDocument) + || t == typeof(JsonElement) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_jsonb], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle generic types + providerSqlType = !dotnetType.IsGenericType + ? null + : dotnetType.GetGenericTypeDefinition() switch + { + Type t + when t == typeof(Dictionary<,>) + || t == typeof(IDictionary<,>) + || t == typeof(List<>) + || t == typeof(IList<>) + || t == typeof(Collection<>) + || t == typeof(ICollection<>) + || t == typeof(IEnumerable<>) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_jsonb], + Type t when t.Name.StartsWith("NpgsqlRange") + => dotnetType.GetGenericArguments().First() switch + { + Type at when at == typeof(DateOnly) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_daterange], + Type at when at == typeof(int) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int4range], + Type at when at == typeof(long) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_int8range], + Type at when at == typeof(decimal) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_numrange], + Type at when at == typeof(float) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_numrange], + Type at when at == typeof(double) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_numrange], + Type at when at == typeof(DateTime) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_tsrange], + Type at when at == typeof(DateTimeOffset) + => ProviderSqlTypeLookup[PostgreSqlTypes.sql_tstzrange], + _ => null + }, + _ => null + }; + + if (providerSqlType != null) + return true; + + // Handle Npgsql types + switch (dotnetType.FullName) + { + case "System.Net.IPAddress": + case "NpgsqlTypes.NpgsqlInet": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_inet]; + break; + case "NpgsqlTypes.NpgsqlCidr": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_cidr]; + break; + case "System.Net.NetworkInformation.PhysicalAddress": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_macaddr8]; + // providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_macaddr]; + break; + case "NpgsqlTypes.NpgsqlPoint": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_point]; + break; + case "NpgsqlTypes.NpgsqlLSeg": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_lseg]; + break; + case "NpgsqlTypes.NpgsqlPath": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_path]; + break; + case "NpgsqlTypes.NpgsqlPolygon": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_polygon]; + break; + case "NpgsqlTypes.NpgsqlLine": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_line]; + break; + case "NpgsqlTypes.NpgsqlCircle": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_circle]; + break; + case "NpgsqlTypes.NpgsqlBox": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_box]; + break; + case "NetTopologySuite.Geometries.Geometry": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_geometry]; + break; + case "NpgsqlTypes.NpgsqlInterval": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_interval]; + break; + case "NpgsqlTypes.NpgsqlTid": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_tid]; + break; + case "NpgsqlTypes.NpgsqlTsQuery": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_tsquery]; + break; + case "NpgsqlTypes.NpgsqlTsVector": + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_tsvector]; + break; + } + + if (providerSqlType != null) + return true; + + // handle array types + var elementType = dotnetType.IsArray ? dotnetType.GetElementType() : null; + if ( + elementType != null + && TryGetProviderSqlTypeMatchingDotnetTypeInternal( + new DbProviderDotnetTypeDescriptor(elementType), + out var elementProviderSqlType + ) + && elementProviderSqlType != null + ) + { + switch (elementProviderSqlType.Name) + { + case PostgreSqlTypes.sql_tsrange: + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_tsmultirange]; + break; + case PostgreSqlTypes.sql_numrange: + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_nummultirange]; + break; + case PostgreSqlTypes.sql_daterange: + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_datemultirange]; + break; + case PostgreSqlTypes.sql_int4range: + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_int4multirange]; + break; + case PostgreSqlTypes.sql_int8range: + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_int8multirange]; + break; + case PostgreSqlTypes.sql_tstzrange: + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_tstzmultirange]; + break; + default: + // in postgresql, we can have array types that end with [] or ARRA + providerSqlType = new DbProviderSqlType( + DbProviderSqlTypeAffinity.Other, + $"{elementProviderSqlType.Name}[]" + ); + break; + } + } + + if (providerSqlType != null) + return true; + + // handle POCO type + if (dotnetType.IsClass || dotnetType.IsInterface) + { + providerSqlType = ProviderSqlTypeLookup[PostgreSqlTypes.sql_jsonb]; + } + + return providerSqlType != null; + } + + protected override bool TryGetProviderSqlTypeFromFullSqlTypeName( + string fullSqlType, + out DbProviderSqlType? providerSqlType + ) + { + if (base.TryGetProviderSqlTypeFromFullSqlTypeName(fullSqlType, out providerSqlType)) + return true; + + providerSqlType = null; + + // PostgreSql (unlike other providers) supports array types for (almost) all its types + if (fullSqlType.EndsWith("[]", StringComparison.OrdinalIgnoreCase)) + { + var elementTypeName = fullSqlType.Substring(0, fullSqlType.Length - 2); + if ( + TryGetProviderSqlTypeFromFullSqlTypeName( + elementTypeName, + out var elementProviderSqlType + ) + && elementProviderSqlType != null + ) + { + providerSqlType = new DbProviderSqlType( + DbProviderSqlTypeAffinity.Other, + $"{elementProviderSqlType.Name}[]" + ); + return true; + } + } + + return false; + } } diff --git a/src/DapperMatic/Providers/PostgreSql/PostgreSqlTypes.cs b/src/DapperMatic/Providers/PostgreSql/PostgreSqlTypes.cs index 85b0e8c..d710ff6 100644 --- a/src/DapperMatic/Providers/PostgreSql/PostgreSqlTypes.cs +++ b/src/DapperMatic/Providers/PostgreSql/PostgreSqlTypes.cs @@ -19,7 +19,7 @@ public static class PostgreSqlTypes public const string sql_int8 = "int8"; public const string sql_bigserial = "bigserial"; public const string sql_serial8 = "serial8"; - + // real public const string sql_float4 = "float4"; public const string sql_real = "real"; @@ -28,7 +28,7 @@ public static class PostgreSqlTypes public const string sql_money = "money"; public const string sql_numeric = "numeric"; public const string sql_decimal = "decimal"; - + // bool public const string sql_bool = "bool"; public const string sql_boolean = "boolean"; @@ -44,7 +44,7 @@ public static class PostgreSqlTypes public const string sql_timestamp = "timestamp"; public const string sql_timestamp_with_time_zone = "timestamp with time zone"; public const string sql_timestamptz = "timestamptz"; - + // text public const string sql_bit = "bit"; public const string sql_bit_varying = "bit varying"; @@ -61,10 +61,10 @@ public static class PostgreSqlTypes public const string sql_jsonb = "jsonb"; public const string sql_jsonpath = "jsonpath"; public const string sql_xml = "xml"; - + // binary public const string sql_bytea = "bytea"; - + // geometry public const string sql_box = "box"; public const string sql_circle = "circle"; @@ -119,4 +119,4 @@ public static class PostgreSqlTypes public const string sql_txid_snapshot = "txid_snapshot"; public const string sql_xid = "xid"; public const string sql_xid8 = "xid8"; -} \ No newline at end of file +} diff --git a/src/DapperMatic/Providers/ProviderSqlType.cs b/src/DapperMatic/Providers/ProviderSqlType.cs deleted file mode 100644 index 0a84aca..0000000 --- a/src/DapperMatic/Providers/ProviderSqlType.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace DapperMatic.Providers; - -/// -/// The provider SQL type. -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -public class ProviderSqlType( - ProviderSqlTypeAffinity affinity, - string name, - Type? recommendedDotnetType = null, - string? aliasOf = null, - string? formatWithLength = null, - string? formatWithPrecision = null, - string? formatWithPrecisionAndScale = null, - int? defaultLength = null, - int? defaultPrecision = null, - int? defaultScale = null, - bool canUseToAutoIncrement = false, - bool autoIncrementsAutomatically = false, - double? minValue = null, - double? maxValue = null, - bool includesTimeZone = false, - bool isDateOnly = false, - bool isTimeOnly = false, - bool isYearOnly = false, - bool isMaxStringLengthType = false, - bool isFixedLength = false, - bool isGuidOnly = false) -{ - public ProviderSqlTypeAffinity Affinity { get; init; } = affinity; - public string Name { get; init; } = name; - public Type? RecommendedDotnetType { get; init; } = recommendedDotnetType; - public string? AliasOf { get; set; } = aliasOf; - public string? FormatWithLength { get; init; } = formatWithLength; - public string? FormatWithPrecision { get; init; } = formatWithPrecision; - public string? FormatWithPrecisionAndScale { get; init; } = formatWithPrecisionAndScale; - public int? DefaultLength { get; set; } = defaultLength; - public int? DefaultPrecision { get; set; } = defaultPrecision; - public int? DefaultScale { get; set; } = defaultScale; - public bool CanUseToAutoIncrement { get; init; } = canUseToAutoIncrement; - public bool AutoIncrementsAutomatically { get; init; } = autoIncrementsAutomatically; - public double? MinValue { get; init; } = minValue; - public double? MaxValue { get; init; } = maxValue; - public bool IncludesTimeZone { get; init; } = includesTimeZone; - public bool IsDateOnly { get; init; } = isDateOnly; - public bool IsTimeOnly { get; init; } = isTimeOnly; - public bool IsYearOnly { get; init; } = isYearOnly; - public bool IsMaxStringLengthType { get; init; } = isMaxStringLengthType; - public bool IsFixedLength { get; init; } = isFixedLength; - public bool IsGuidOnly { get; init; } = isGuidOnly; -} - - -public static class ProviderSqlTypeExtensions -{ - public static bool SupportsLength(this ProviderSqlType providerSqlType) => - !string.IsNullOrWhiteSpace(providerSqlType.FormatWithLength); - - public static bool SupportsPrecision(this ProviderSqlType providerSqlType) => - !string.IsNullOrWhiteSpace(providerSqlType.FormatWithPrecision); - - public static bool SupportsPrecisionAndScale(this ProviderSqlType providerSqlType) => - !string.IsNullOrWhiteSpace(providerSqlType.FormatWithPrecisionAndScale); -} \ No newline at end of file diff --git a/src/DapperMatic/Providers/ProviderTypeMapBase.cs b/src/DapperMatic/Providers/ProviderTypeMapBase.cs deleted file mode 100644 index addcc24..0000000 --- a/src/DapperMatic/Providers/ProviderTypeMapBase.cs +++ /dev/null @@ -1,992 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.ObjectModel; - -namespace DapperMatic.Providers; - -public abstract class ProviderTypeMapBase : IProviderTypeMap -{ - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once CollectionNeverUpdated.Global - public static readonly ConcurrentDictionary> TypeMaps = - new(); - - protected abstract DbProviderType ProviderType { get; } - protected abstract ProviderSqlType[] ProviderSqlTypes { get; } - - public virtual bool TryGetRecommendedDotnetTypeMatchingSqlType( - string fullSqlType, - out ( - Type dotnetType, - int? length, - int? precision, - int? scale, - bool? isAutoIncrementing, - Type[] allSupportedTypes - )? recommendedDotnetType - ) - { - recommendedDotnetType = null; - - if (TypeMaps.TryGetValue(ProviderType, out var additionalTypeMaps)) - { - foreach (var typeMap in additionalTypeMaps) - { - if (typeMap.TryGetRecommendedDotnetTypeMatchingSqlType(fullSqlType, out var rdt)) - { - recommendedDotnetType = rdt; - return true; - } - } - } - - // perform some detective reasoning to pinpoint a recommended type - var numbers = fullSqlType.ExtractNumbers(); - - // try to find a sql provider type match - var fullSqlTypeAlpha = fullSqlType.ToAlpha(); - var sqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.ToAlpha().Equals(fullSqlTypeAlpha, StringComparison.OrdinalIgnoreCase) - ); - if (sqlType == null) - return false; - - var isAutoIncrementing = sqlType.AutoIncrementsAutomatically; - - switch (sqlType.Affinity) - { - case ProviderSqlTypeAffinity.Binary: - recommendedDotnetType = (typeof(byte[]), null, null, null, null, [typeof(byte[])]); - break; - case ProviderSqlTypeAffinity.Boolean: - recommendedDotnetType = ( - typeof(bool), - null, - null, - null, - null, - [ - typeof(bool), - typeof(short), - typeof(int), - typeof(long), - typeof(ushort), - typeof(uint), - typeof(ulong), - typeof(string) - ] - ); - break; - case ProviderSqlTypeAffinity.DateTime: - if (sqlType.IsDateOnly == true) - recommendedDotnetType = ( - typeof(DateOnly), - null, - null, - null, - null, - [typeof(DateOnly), typeof(DateTime), typeof(string)] - ); - else if (sqlType.IsTimeOnly == true) - recommendedDotnetType = ( - typeof(TimeOnly), - null, - null, - null, - null, - [typeof(TimeOnly), typeof(DateTime), typeof(string)] - ); - else if (sqlType.IsYearOnly == true) - recommendedDotnetType = ( - typeof(int), - null, - null, - null, - null, - [ - typeof(short), - typeof(int), - typeof(long), - typeof(ushort), - typeof(uint), - typeof(ulong), - typeof(string) - ] - ); - else if (sqlType.IncludesTimeZone == true) - recommendedDotnetType = ( - typeof(DateTimeOffset), - null, - null, - null, - null, - [typeof(DateTimeOffset), typeof(DateTime), typeof(string)] - ); - else - recommendedDotnetType = ( - typeof(DateTime), - null, - null, - null, - null, - [typeof(DateTime), typeof(DateTimeOffset), typeof(string)] - ); - break; - case ProviderSqlTypeAffinity.Integer: - int? intPrecision = numbers.Length > 0 ? numbers[0] : null; - if (sqlType.MinValue.HasValue && sqlType.MinValue == 0) - { - if (sqlType.MaxValue.HasValue) - { - if (sqlType.MaxValue.Value <= ushort.MaxValue) - recommendedDotnetType = ( - typeof(ushort), - null, - intPrecision, - null, - isAutoIncrementing, - [ - typeof(short), - typeof(int), - typeof(long), - typeof(ushort), - typeof(uint), - typeof(ulong), - typeof(string) - ] - ); - else if (sqlType.MaxValue.Value <= uint.MaxValue) - recommendedDotnetType = ( - typeof(uint), - null, - intPrecision, - null, - isAutoIncrementing, - [ - typeof(int), - typeof(long), - typeof(uint), - typeof(ulong), - typeof(string) - ] - ); - else if (sqlType.MaxValue.Value <= ulong.MaxValue) - recommendedDotnetType = ( - typeof(ulong), - null, - intPrecision, - null, - isAutoIncrementing, - [typeof(long), typeof(ulong), typeof(string)] - ); - } - if (recommendedDotnetType == null) - { - recommendedDotnetType = ( - typeof(uint), - null, - intPrecision, - null, - isAutoIncrementing, - [typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(string)] - ); - } - } - if (recommendedDotnetType == null) - { - if (sqlType.MaxValue.HasValue) - { - if (sqlType.MaxValue.Value <= short.MaxValue) - recommendedDotnetType = ( - typeof(short), - null, - intPrecision, - null, - isAutoIncrementing, - [typeof(short), typeof(int), typeof(long), typeof(string)] - ); - else if (sqlType.MaxValue.Value <= int.MaxValue) - recommendedDotnetType = ( - typeof(int), - null, - intPrecision, - null, - isAutoIncrementing, - [typeof(int), typeof(long), typeof(string)] - ); - else if (sqlType.MaxValue.Value <= long.MaxValue) - recommendedDotnetType = ( - typeof(long), - null, - intPrecision, - null, - isAutoIncrementing, - [typeof(long), typeof(string)] - ); - } - if (recommendedDotnetType == null) - { - recommendedDotnetType = ( - typeof(int), - null, - intPrecision, - null, - isAutoIncrementing, - [typeof(int), typeof(long), typeof(string)] - ); - } - } - break; - case ProviderSqlTypeAffinity.Real: - int? precision = numbers.Length > 0 ? numbers[0] : null; - int? scale = numbers.Length > 1 ? numbers[1] : null; - recommendedDotnetType = ( - typeof(decimal), - null, - precision, - scale, - isAutoIncrementing, - [typeof(decimal), typeof(float), typeof(double), typeof(string)] - ); - break; - case ProviderSqlTypeAffinity.Text: - int? length = numbers.Length > 0 ? numbers[0] : null; - if (length > 8000) - length = int.MaxValue; - recommendedDotnetType = ( - typeof(string), - null, - length, - null, - null, - [typeof(string)] - ); - break; - case ProviderSqlTypeAffinity.Geometry: - case ProviderSqlTypeAffinity.RangeType: - case ProviderSqlTypeAffinity.Other: - if ( - sqlType.Name.Contains("json", StringComparison.OrdinalIgnoreCase) - || sqlType.Name.Contains("xml", StringComparison.OrdinalIgnoreCase) - ) - recommendedDotnetType = ( - typeof(string), - null, - null, - null, - null, - [typeof(string)] - ); - else - recommendedDotnetType = ( - typeof(object), - null, - null, - null, - null, - [typeof(object), typeof(string)] - ); - break; - } - - return recommendedDotnetType != null; - } - - public virtual bool TryGetRecommendedSqlTypeMatchingDotnetType( - Type dotnetType, - int? length, - int? precision, - int? scale, - bool? autoIncrement, - out ProviderSqlType? recommendedSqlType - ) - { - recommendedSqlType = null; - - if (TypeMaps.TryGetValue(ProviderType, out var additionalTypeMaps)) - { - foreach (var typeMap in additionalTypeMaps) - { - if ( - typeMap.TryGetRecommendedSqlTypeMatchingDotnetType( - dotnetType, - length, - precision, - scale, - autoIncrement, - out var rdt - ) - ) - { - recommendedSqlType = rdt; - return true; - } - } - } - - if (ProviderType == DbProviderType.PostgreSql) - { - // Handle well-known types - var typeName = dotnetType.Name; - switch (typeName) - { - case "IPAddress": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("inet", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlCidr4": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("cidr", StringComparison.OrdinalIgnoreCase) - ); - break; - case "PhysicalAddress": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("macaddr", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlTsQuery": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("tsquery", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlTsVector": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("tsvector", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlPoint": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("point", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlLSeg": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("lseg", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlPath": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("path", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlPolygon": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("polygon", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlLine": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("line", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlCircle": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("circle", StringComparison.OrdinalIgnoreCase) - ); - break; - case "NpgsqlBox": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("box", StringComparison.OrdinalIgnoreCase) - ); - break; - case "PostgisGeometry": - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("geometry", StringComparison.OrdinalIgnoreCase) - ); - break; - } - - if ( - dotnetType == typeof(Dictionary) - || dotnetType == typeof(IDictionary) - ) - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Name.Equals("hstore", StringComparison.OrdinalIgnoreCase) - ); - - if (recommendedSqlType != null) - return true; - } - - // the dotnetType could be a nullable type, so we need to check for that - // and get the underlying type - if (dotnetType.IsGenericType && dotnetType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - dotnetType = Nullable.GetUnderlyingType(dotnetType)!; - } - - // We're trying to find the right type to use as a lookup type - // IDictionary<,> Dictionary<,> IEnumerable<> ICollection<> List<> object[] - if (dotnetType.IsArray) - { - // dotnetType = dotnetType.GetElementType()!; - dotnetType = typeof(object[]); - } - else if ( - dotnetType.IsGenericType - && dotnetType.GetGenericTypeDefinition() == typeof(List<>) - ) - { - // dotnetType = dotnetType.GetGenericArguments()[0]; - dotnetType = typeof(List<>); - } - else if ( - dotnetType.IsGenericType - && dotnetType.GetGenericTypeDefinition() == typeof(IDictionary<,>) - ) - { - // dotnetType = dotnetType.GetGenericArguments()[1]; - dotnetType = typeof(IDictionary<,>); - } - else if ( - dotnetType.IsGenericType - && dotnetType.GetGenericTypeDefinition() == typeof(Dictionary<,>) - ) - { - // dotnetType = dotnetType.GetGenericArguments()[1]; - dotnetType = typeof(Dictionary<,>); - } - else if ( - dotnetType.IsGenericType - && dotnetType.GetGenericTypeDefinition() == typeof(IEnumerable<>) - ) - { - // dotnetType = dotnetType.GetGenericArguments()[0]; - dotnetType = typeof(IEnumerable<>); - } - else if ( - dotnetType.IsGenericType - && dotnetType.GetGenericTypeDefinition() == typeof(ICollection<>) - ) - { - // dotnetType = dotnetType.GetGenericArguments()[0]; - dotnetType = typeof(ICollection<>); - } - else if ( - dotnetType.IsGenericType - && dotnetType.GetGenericTypeDefinition() == typeof(IList<>) - ) - { - // dotnetType = dotnetType.GetGenericArguments()[0]; - dotnetType = typeof(IList<>); - } - else if (dotnetType.IsGenericType) - { - // could probably just stick with this, but the above - // is more explicit for now - dotnetType = dotnetType.GetGenericTypeDefinition(); - } - - // WARNING!! The following showcases why the order within each affinity group of the provider sql types matters, as the recommended type - // is going to be the first match for the given scenario - switch (dotnetType) - { - case not null when dotnetType == typeof(sbyte): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(sbyte.MinValue) <= sbyte.MinValue - && t.MaxValue.GetValueOrDefault(sbyte.MaxValue) >= sbyte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(sbyte.MaxValue) >= sbyte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(sbyte.MinValue) <= sbyte.MinValue - && t.MaxValue.GetValueOrDefault(sbyte.MaxValue) >= sbyte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(sbyte.MaxValue) >= sbyte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(sbyte.MinValue) <= sbyte.MinValue - && t.MaxValue.GetValueOrDefault(sbyte.MaxValue) >= sbyte.MaxValue - ); - break; - case not null when dotnetType == typeof(byte): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(byte.MinValue) <= byte.MinValue - && t.MaxValue.GetValueOrDefault(byte.MaxValue) >= byte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(byte.MaxValue) >= byte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(byte.MinValue) <= byte.MinValue - && t.MaxValue.GetValueOrDefault(byte.MaxValue) >= byte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(byte.MaxValue) >= byte.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(byte.MinValue) <= byte.MinValue - && t.MaxValue.GetValueOrDefault(byte.MaxValue) >= byte.MaxValue - ); - break; - case not null when dotnetType == typeof(short): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(short.MinValue) <= short.MinValue - && t.MaxValue.GetValueOrDefault(short.MaxValue) >= short.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(short.MaxValue) >= short.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(short.MinValue) <= short.MinValue - && t.MaxValue.GetValueOrDefault(short.MaxValue) >= short.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(short.MaxValue) >= short.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(short.MinValue) <= short.MinValue - && t.MaxValue.GetValueOrDefault(short.MaxValue) >= short.MaxValue - ); - break; - case not null when dotnetType == typeof(int): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(int.MinValue) <= int.MinValue - && t.MaxValue.GetValueOrDefault(int.MaxValue) >= int.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(int.MaxValue) >= int.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(int.MinValue) <= int.MinValue - && t.MaxValue.GetValueOrDefault(int.MaxValue) >= int.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(int.MaxValue) >= int.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(int.MinValue) <= int.MinValue - && t.MaxValue.GetValueOrDefault(int.MaxValue) >= int.MaxValue - ); - break; - case not null when dotnetType == typeof(long): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(long.MinValue) <= long.MinValue - && t.MaxValue.GetValueOrDefault(long.MaxValue) >= long.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(long.MaxValue) >= long.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(long.MinValue) <= long.MinValue - && t.MaxValue.GetValueOrDefault(long.MaxValue) >= long.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(long.MaxValue) >= long.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(long.MinValue) <= long.MinValue - && t.MaxValue.GetValueOrDefault(long.MaxValue) >= long.MaxValue - ); - break; - case not null when dotnetType == typeof(ushort): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(ushort.MinValue) <= ushort.MinValue - && t.MaxValue.GetValueOrDefault(ushort.MaxValue) >= ushort.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(ushort.MaxValue) >= ushort.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(ushort.MinValue) <= ushort.MinValue - && t.MaxValue.GetValueOrDefault(ushort.MaxValue) >= ushort.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(ushort.MaxValue) >= ushort.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(0) == 0 - && t.MaxValue.GetValueOrDefault(ushort.MaxValue) >= ushort.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(ushort.MaxValue) >= ushort.MaxValue - ); - break; - case not null when dotnetType == typeof(uint): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(uint.MinValue) <= uint.MinValue - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(uint.MinValue) <= uint.MinValue - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(0) == 0 - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ); - break; - case not null when dotnetType == typeof(ulong): - if (autoIncrement.GetValueOrDefault(false)) - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MinValue.GetValueOrDefault(ulong.MinValue) <= ulong.MinValue - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.AutoIncrementsAutomatically - && t.MaxValue.GetValueOrDefault(long.MaxValue) >= long.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MinValue.GetValueOrDefault(ulong.MinValue) <= ulong.MinValue - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.CanUseToAutoIncrement - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer && t.CanUseToAutoIncrement - ); - else - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(0) == 0 - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ); - break; - case not null when dotnetType == typeof(bool): - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Boolean - ); - break; - case not null when dotnetType == typeof(decimal): - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.MinValue.GetValueOrDefault((double)decimal.MinValue) - <= (double)decimal.MinValue - && t.MaxValue.GetValueOrDefault((double)decimal.MaxValue) - >= (double)decimal.MaxValue - ); - break; - case not null when dotnetType == typeof(double): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("double", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Contains("double", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.MinValue.GetValueOrDefault(double.MinValue) <= double.MinValue - && t.MaxValue.GetValueOrDefault(double.MaxValue) >= double.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("float", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("numeric", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("decimal", StringComparison.OrdinalIgnoreCase) - ); - break; - case not null when dotnetType == typeof(float): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("float", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Contains("float", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.MinValue.GetValueOrDefault(float.MinValue) <= float.MinValue - && t.MaxValue.GetValueOrDefault(float.MaxValue) >= float.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("numeric", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Real - && t.Name.Equals("decimal", StringComparison.OrdinalIgnoreCase) - ); - break; - case not null when dotnetType == typeof(DateTime): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime && t.IncludesTimeZone != true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime - ); - break; - case not null when dotnetType == typeof(DateTimeOffset): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime && t.IncludesTimeZone == true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime - ); - break; - case not null when dotnetType == typeof(DateOnly): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime && t.IsDateOnly == true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime - ); - break; - case not null when dotnetType == typeof(TimeOnly): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime && t.IsTimeOnly == true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.DateTime - ); - break; - case not null when dotnetType == typeof(TimeSpan): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MinValue.GetValueOrDefault(0) == 0 - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(ulong.MaxValue) >= ulong.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(uint.MaxValue) > uint.MaxValue - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Integer - && t.MaxValue.GetValueOrDefault(uint.MaxValue) >= uint.MaxValue - ); - break; - case not null when dotnetType == typeof(byte[]): - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Binary - ); - break; - case not null when dotnetType == typeof(Guid): - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text && t.IsGuidOnly == true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && !string.IsNullOrWhiteSpace(t.FormatWithLength) - && t.IsFixedLength == true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && !string.IsNullOrWhiteSpace(t.FormatWithLength) - && t.IsFixedLength != true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text && t.IsFixedLength != true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - ); - break; - case not null when dotnetType == typeof(string): - case not null when dotnetType == typeof(char[]): - if (length.HasValue && length.Value > 8000) - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && t.IsMaxStringLengthType == true - && t.IsFixedLength != true - ); - if (recommendedSqlType == null && length.HasValue && length.Value <= 8000) - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && !string.IsNullOrWhiteSpace(t.FormatWithLength) - && t.IsFixedLength != true - ); - if (recommendedSqlType == null) - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text && t.IsFixedLength != true - ); - break; - case not null when dotnetType == typeof(Dictionary<,>): - case not null when dotnetType == typeof(IDictionary<,>): - case not null when dotnetType == typeof(IEnumerable<>): - case not null when dotnetType == typeof(ICollection<>): - case not null when dotnetType == typeof(List<>): - case not null when dotnetType == typeof(IList<>): - case not null when dotnetType == typeof(object[]): - case not null when dotnetType == typeof(object): - case not null when dotnetType.IsClass: - recommendedSqlType = - ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && t.Name.Contains("json", StringComparison.OrdinalIgnoreCase) - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && t.IsMaxStringLengthType == true - && t.IsFixedLength != true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text - && !string.IsNullOrWhiteSpace(t.FormatWithLength) - && t.IsFixedLength != true - ) - ?? ProviderSqlTypes.FirstOrDefault(t => - t.Affinity == ProviderSqlTypeAffinity.Text && t.IsFixedLength != true - ); - break; - } - - if (recommendedSqlType == null) - { - // couldn't find the appropriate type, so we'll just use the first one that matches the requested type - // if such exists (NOT IDEAL!!) - recommendedSqlType = ProviderSqlTypes.FirstOrDefault(t => - t.RecommendedDotnetType == dotnetType - ); - } - - return recommendedSqlType != null; - } -} diff --git a/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Strings.cs b/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Strings.cs index 5a61489..a535bb9 100644 --- a/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Strings.cs +++ b/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Strings.cs @@ -1,3 +1,5 @@ +using DapperMatic.Models; + namespace DapperMatic.Providers.SqlServer; public partial class SqlServerMethods @@ -41,26 +43,26 @@ string newTableName protected override (string sql, object parameters) SqlGetViewNames( string? schemaName, - string? viewNameFilter = null) + string? viewNameFilter = null + ) { var where = string.IsNullOrWhiteSpace(viewNameFilter) ? "" : ToLikeString(viewNameFilter); - var sql = - $""" - - SELECT - v.[name] AS ViewName - FROM sys.objects v - INNER JOIN sys.sql_modules m ON v.object_id = m.object_id - WHERE - v.[type] = 'V' - AND v.is_ms_shipped = 0 - AND SCHEMA_NAME(v.schema_id) = @schemaName - {(string.IsNullOrWhiteSpace(where) ? "" : " AND v.[name] LIKE @where")} - ORDER BY - SCHEMA_NAME(v.schema_id), - v.[name] - """; + var sql = $""" + + SELECT + v.[name] AS ViewName + FROM sys.objects v + INNER JOIN sys.sql_modules m ON v.object_id = m.object_id + WHERE + v.[type] = 'V' + AND v.is_ms_shipped = 0 + AND SCHEMA_NAME(v.schema_id) = @schemaName + {(string.IsNullOrWhiteSpace(where) ? "" : " AND v.[name] LIKE @where")} + ORDER BY + SCHEMA_NAME(v.schema_id), + v.[name] + """; return (sql, new { schemaName = NormalizeSchemaName(schemaName), where }); } @@ -72,24 +74,23 @@ protected override (string sql, object parameters) SqlGetViews( { var where = string.IsNullOrWhiteSpace(viewNameFilter) ? "" : ToLikeString(viewNameFilter); - var sql = - $""" - - SELECT - SCHEMA_NAME(v.schema_id) AS SchemaName, - v.[name] AS ViewName, - m.definition AS Definition - FROM sys.objects v - INNER JOIN sys.sql_modules m ON v.object_id = m.object_id - WHERE - v.[type] = 'V' - AND v.is_ms_shipped = 0 - AND SCHEMA_NAME(v.schema_id) = @schemaName - {(string.IsNullOrWhiteSpace(where) ? "" : " AND v.[name] LIKE @where")} - ORDER BY - SCHEMA_NAME(v.schema_id), - v.[name] - """; + var sql = $""" + + SELECT + SCHEMA_NAME(v.schema_id) AS SchemaName, + v.[name] AS ViewName, + m.definition AS Definition + FROM sys.objects v + INNER JOIN sys.sql_modules m ON v.object_id = m.object_id + WHERE + v.[type] = 'V' + AND v.is_ms_shipped = 0 + AND SCHEMA_NAME(v.schema_id) = @schemaName + {(string.IsNullOrWhiteSpace(where) ? "" : " AND v.[name] LIKE @where")} + ORDER BY + SCHEMA_NAME(v.schema_id), + v.[name] + """; return (sql, new { schemaName = NormalizeSchemaName(schemaName), where }); } @@ -109,12 +110,14 @@ protected override string NormalizeViewDefinition(string definition) if (i == definition.Length - 2) break; - if (!WhiteSpaceCharacters.Contains(definition[i - 1]) + if ( + !WhiteSpaceCharacters.Contains(definition[i - 1]) || char.ToUpperInvariant(definition[i]) != 'A' || char.ToUpperInvariant(definition[i + 1]) != 'S' - || !WhiteSpaceCharacters.Contains(definition[i + 2])) + || !WhiteSpaceCharacters.Contains(definition[i + 2]) + ) continue; - + indexOfAs = i; break; } diff --git a/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Tables.cs b/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Tables.cs index a10dfa1..24a6e40 100644 --- a/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Tables.cs +++ b/src/DapperMatic/Providers/SqlServer/SqlServerMethods.Tables.cs @@ -389,14 +389,17 @@ string default_expression ) ?.i; - var (dotnetType, _, _, _, _, _) = GetDotnetTypeFromSqlType(tableColumn.data_type); + var dotnetTypeDescriptor = GetDotnetTypeFromSqlType(tableColumn.data_type); var column = new DxColumn( tableColumn.schema_name, tableColumn.table_name, tableColumn.column_name, - dotnetType, - tableColumn.data_type, + dotnetTypeDescriptor.DotnetType, + new Dictionary + { + { ProviderType, tableColumn.data_type } + }, tableColumn.max_length, tableColumn.numeric_precision, tableColumn.numeric_scale, diff --git a/src/DapperMatic/Providers/SqlServer/SqlServerMethods.cs b/src/DapperMatic/Providers/SqlServer/SqlServerMethods.cs index f81661e..e493182 100644 --- a/src/DapperMatic/Providers/SqlServer/SqlServerMethods.cs +++ b/src/DapperMatic/Providers/SqlServer/SqlServerMethods.cs @@ -8,7 +8,7 @@ public partial class SqlServerMethods : DatabaseMethodsBase, IDatabaseMethods { public override DbProviderType ProviderType => DbProviderType.SqlServer; - public override IProviderTypeMap ProviderTypeMap => SqlServerProviderTypeMap.Instance.Value; + public override IDbProviderTypeMap ProviderTypeMap => SqlServerProviderTypeMap.Instance.Value; private static string _defaultSchema = "dbo"; protected override string DefaultSchema => _defaultSchema; @@ -36,7 +36,7 @@ public override async Task GetDatabaseVersionAsync( const string sql = "SELECT SERVERPROPERTY('Productversion')"; var versionString = await ExecuteScalarAsync(db, sql, tx: tx).ConfigureAwait(false) ?? ""; - return ProviderUtils.ExtractVersionFromVersionString(versionString); + return DbProviderUtils.ExtractVersionFromVersionString(versionString); } public override char[] QuoteChars => ['[', ']']; diff --git a/src/DapperMatic/Providers/SqlServer/SqlServerProviderTypeMap.cs b/src/DapperMatic/Providers/SqlServer/SqlServerProviderTypeMap.cs index a6f6799..d8de819 100644 --- a/src/DapperMatic/Providers/SqlServer/SqlServerProviderTypeMap.cs +++ b/src/DapperMatic/Providers/SqlServer/SqlServerProviderTypeMap.cs @@ -1,6 +1,13 @@ +using System.Collections; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Text.Json; +using System.Text.Json.Nodes; + namespace DapperMatic.Providers.SqlServer; -public sealed class SqlServerProviderTypeMap : ProviderTypeMapBase +public sealed class SqlServerProviderTypeMap : DbProviderTypeMapBase { internal static readonly Lazy Instance = new(() => new SqlServerProviderTypeMap()); @@ -8,43 +15,49 @@ public sealed class SqlServerProviderTypeMap : ProviderTypeMapBase private SqlServerProviderTypeMap() : base() { } - protected override DbProviderType ProviderType => DbProviderType.Sqlite; + protected override DbProviderType ProviderType => DbProviderType.SqlServer; + + public override string SqTypeForStringLengthMax => "nvarchar(max)"; + + public override string SqTypeForBinaryLengthMax => "varbinary(max)"; + + public override string SqlTypeForJson => "nvarchar(max)"; /// /// IMPORTANT!! The order within an affinity group matters, as the first possible match will be used as the recommended sql type for a dotnet type /// - protected override ProviderSqlType[] ProviderSqlTypes => + protected override DbProviderSqlType[] ProviderSqlTypes => [ new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, SqlServerTypes.sql_tinyint, canUseToAutoIncrement: true, minValue: -128, maxValue: 128 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, SqlServerTypes.sql_smallint, canUseToAutoIncrement: true, minValue: -32768, maxValue: 32767 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, SqlServerTypes.sql_int, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647 ), new( - ProviderSqlTypeAffinity.Integer, + DbProviderSqlTypeAffinity.Integer, SqlServerTypes.sql_bigint, canUseToAutoIncrement: true, minValue: -9223372036854775808, maxValue: 9223372036854775807 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, SqlServerTypes.sql_decimal, formatWithPrecision: "decimal({0})", formatWithPrecisionAndScale: "decimal({0},{1})", @@ -52,7 +65,7 @@ private SqlServerProviderTypeMap() defaultScale: 0 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, SqlServerTypes.sql_numeric, formatWithPrecision: "numeric({0})", formatWithPrecisionAndScale: "numeric({0},{1})", @@ -60,92 +73,214 @@ private SqlServerProviderTypeMap() defaultScale: 0 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, SqlServerTypes.sql_float, formatWithPrecision: "float({0})", defaultPrecision: 53, defaultScale: 0 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, SqlServerTypes.sql_real, formatWithPrecision: "real({0})", defaultPrecision: 24, defaultScale: 0 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, SqlServerTypes.sql_money, formatWithPrecision: "money({0})", defaultPrecision: 19, defaultScale: 4 ), new( - ProviderSqlTypeAffinity.Real, + DbProviderSqlTypeAffinity.Real, SqlServerTypes.sql_smallmoney, formatWithPrecision: "smallmoney({0})", defaultPrecision: 10, defaultScale: 4 ), - new(ProviderSqlTypeAffinity.Boolean, SqlServerTypes.sql_bit), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_date, isDateOnly: true), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_datetime), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_smalldatetime), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_datetime2), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_datetimeoffset), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_time, isTimeOnly: true), - new(ProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_timestamp), + new(DbProviderSqlTypeAffinity.Boolean, SqlServerTypes.sql_bit), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_date, isDateOnly: true), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_datetime), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_smalldatetime), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_datetime2), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_datetimeoffset), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_time, isTimeOnly: true), + new(DbProviderSqlTypeAffinity.DateTime, SqlServerTypes.sql_timestamp), new( - ProviderSqlTypeAffinity.Text, - SqlServerTypes.sql_char, - formatWithLength: "char({0})", - defaultLength: 255 + DbProviderSqlTypeAffinity.Text, + SqlServerTypes.sql_nvarchar, + formatWithLength: "nvarchar({0})", + defaultLength: DefaultLength ), new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, SqlServerTypes.sql_varchar, formatWithLength: "varchar({0})", - defaultLength: 255 + defaultLength: DefaultLength ), - new(ProviderSqlTypeAffinity.Text, SqlServerTypes.sql_text), + new(DbProviderSqlTypeAffinity.Text, SqlServerTypes.sql_ntext), + new(DbProviderSqlTypeAffinity.Text, SqlServerTypes.sql_text), new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, SqlServerTypes.sql_nchar, formatWithLength: "nchar({0})", - defaultLength: 255 + defaultLength: DefaultLength ), new( - ProviderSqlTypeAffinity.Text, - SqlServerTypes.sql_nvarchar, - formatWithLength: "nvarchar({0})", - defaultLength: 255 + DbProviderSqlTypeAffinity.Text, + SqlServerTypes.sql_char, + formatWithLength: "char({0})", + defaultLength: DefaultLength ), - new(ProviderSqlTypeAffinity.Text, SqlServerTypes.sql_ntext), new( - ProviderSqlTypeAffinity.Text, + DbProviderSqlTypeAffinity.Text, SqlServerTypes.sql_uniqueidentifier, isGuidOnly: true ), new( - ProviderSqlTypeAffinity.Binary, + DbProviderSqlTypeAffinity.Binary, + SqlServerTypes.sql_varbinary, + formatWithLength: "varbinary({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Binary, SqlServerTypes.sql_binary, formatWithLength: "binary({0})", - defaultLength: 1024 + defaultLength: DefaultLength ), - new( - ProviderSqlTypeAffinity.Binary, - SqlServerTypes.sql_varbinary, - formatWithLength: "varbinary({0})", - defaultLength: 1024 - ), - new(ProviderSqlTypeAffinity.Binary, SqlServerTypes.sql_image), - new(ProviderSqlTypeAffinity.Geometry, SqlServerTypes.sql_geometry), - new(ProviderSqlTypeAffinity.Geometry, SqlServerTypes.sql_geography), - new(ProviderSqlTypeAffinity.Geometry, SqlServerTypes.sql_hierarchyid), - new(ProviderSqlTypeAffinity.Other, SqlServerTypes.sql_variant), - new(ProviderSqlTypeAffinity.Other, SqlServerTypes.sql_xml), - new(ProviderSqlTypeAffinity.Other, SqlServerTypes.sql_cursor), - new(ProviderSqlTypeAffinity.Other, SqlServerTypes.sql_table), - new(ProviderSqlTypeAffinity.Other, SqlServerTypes.sql_json) + new(DbProviderSqlTypeAffinity.Binary, SqlServerTypes.sql_image), + new(DbProviderSqlTypeAffinity.Geometry, SqlServerTypes.sql_geometry), + new(DbProviderSqlTypeAffinity.Geometry, SqlServerTypes.sql_geography), + new(DbProviderSqlTypeAffinity.Geometry, SqlServerTypes.sql_hierarchyid), + new(DbProviderSqlTypeAffinity.Other, SqlServerTypes.sql_variant), + new(DbProviderSqlTypeAffinity.Other, SqlServerTypes.sql_xml), + new(DbProviderSqlTypeAffinity.Other, SqlServerTypes.sql_cursor), + new(DbProviderSqlTypeAffinity.Other, SqlServerTypes.sql_table), + new(DbProviderSqlTypeAffinity.Other, SqlServerTypes.sql_json) ]; + + protected override bool TryGetProviderSqlTypeMatchingDotnetTypeInternal( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ) + { + providerSqlType = null; + + var dotnetType = descriptor.DotnetType; + + // handle well-known types first + providerSqlType = dotnetType.IsGenericType + ? null + : dotnetType switch + { + Type t when t == typeof(bool) => ProviderSqlTypeLookup[SqlServerTypes.sql_bit], + Type t when t == typeof(byte) => ProviderSqlTypeLookup[SqlServerTypes.sql_smallint], + Type t when t == typeof(ReadOnlyMemory) + => ProviderSqlTypeLookup[SqlServerTypes.sql_smallint], + Type t when t == typeof(sbyte) + => ProviderSqlTypeLookup[SqlServerTypes.sql_smallint], + Type t when t == typeof(short) + => ProviderSqlTypeLookup[SqlServerTypes.sql_smallint], + Type t when t == typeof(ushort) + => ProviderSqlTypeLookup[SqlServerTypes.sql_smallint], + Type t when t == typeof(int) => ProviderSqlTypeLookup[SqlServerTypes.sql_int], + Type t when t == typeof(uint) => ProviderSqlTypeLookup[SqlServerTypes.sql_int], + Type t when t == typeof(long) => ProviderSqlTypeLookup[SqlServerTypes.sql_bigint], + Type t when t == typeof(ulong) => ProviderSqlTypeLookup[SqlServerTypes.sql_bigint], + Type t when t == typeof(float) => ProviderSqlTypeLookup[SqlServerTypes.sql_float], + Type t when t == typeof(double) => ProviderSqlTypeLookup[SqlServerTypes.sql_float], + Type t when t == typeof(decimal) + => ProviderSqlTypeLookup[SqlServerTypes.sql_decimal], + Type t when t == typeof(char) + => descriptor.Unicode.GetValueOrDefault(true) + ? ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar] + : ProviderSqlTypeLookup[SqlServerTypes.sql_varchar], + Type t when t == typeof(string) + => descriptor.Unicode.GetValueOrDefault(true) + ? ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar] + : ProviderSqlTypeLookup[SqlServerTypes.sql_varchar], + Type t when t == typeof(char[]) + => descriptor.Unicode.GetValueOrDefault(true) + ? ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar] + : ProviderSqlTypeLookup[SqlServerTypes.sql_varchar], + Type t when t == typeof(ReadOnlyMemory[]) + => descriptor.Unicode.GetValueOrDefault(true) + ? ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar] + : ProviderSqlTypeLookup[SqlServerTypes.sql_varchar], + Type t when t == typeof(Stream) + => descriptor.Unicode.GetValueOrDefault(true) + ? ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar] + : ProviderSqlTypeLookup[SqlServerTypes.sql_varchar], + Type t when t == typeof(TextReader) + => descriptor.Unicode.GetValueOrDefault(true) + ? ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar] + : ProviderSqlTypeLookup[SqlServerTypes.sql_varchar], + Type t when t == typeof(byte[]) + => ProviderSqlTypeLookup[SqlServerTypes.sql_varbinary], + Type t when t == typeof(object) + => ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar], + Type t when t == typeof(object[]) + => ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar], + Type t when t == typeof(Guid) + => ProviderSqlTypeLookup[SqlServerTypes.sql_uniqueidentifier], + Type t when t == typeof(DateTime) + => ProviderSqlTypeLookup[SqlServerTypes.sql_datetime], + Type t when t == typeof(DateTimeOffset) + => ProviderSqlTypeLookup[SqlServerTypes.sql_datetimeoffset], + Type t when t == typeof(TimeSpan) + => ProviderSqlTypeLookup[SqlServerTypes.sql_bigint], + Type t when t == typeof(DateOnly) => ProviderSqlTypeLookup[SqlServerTypes.sql_date], + Type t when t == typeof(TimeOnly) => ProviderSqlTypeLookup[SqlServerTypes.sql_time], + Type t when t == typeof(BitArray) || t == typeof(BitVector32) + => ProviderSqlTypeLookup[SqlServerTypes.sql_varbinary], + Type t + when t == typeof(ImmutableDictionary) + || t == typeof(Dictionary) + || t == typeof(IDictionary) + => ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar], + Type t + when t == typeof(JsonNode) + || t == typeof(JsonObject) + || t == typeof(JsonArray) + || t == typeof(JsonValue) + || t == typeof(JsonDocument) + || t == typeof(JsonElement) + => ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle generic types + providerSqlType = !dotnetType.IsGenericType + ? null + : dotnetType.GetGenericTypeDefinition() switch + { + Type t + when t == typeof(Dictionary<,>) + || t == typeof(IDictionary<,>) + || t == typeof(List<>) + || t == typeof(IList<>) + || t == typeof(Collection<>) + || t == typeof(ICollection<>) + || t == typeof(IEnumerable<>) + => ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle POCO type + if (dotnetType.IsClass || dotnetType.IsInterface) + { + providerSqlType = ProviderSqlTypeLookup[SqlServerTypes.sql_nvarchar]; + } + + return providerSqlType != null; + } } diff --git a/src/DapperMatic/Providers/Sqlite/SqliteMethods.Strings.cs b/src/DapperMatic/Providers/Sqlite/SqliteMethods.Strings.cs index 50c55c1..44ded9c 100644 --- a/src/DapperMatic/Providers/Sqlite/SqliteMethods.Strings.cs +++ b/src/DapperMatic/Providers/Sqlite/SqliteMethods.Strings.cs @@ -15,12 +15,13 @@ protected override string SqlInlineColumnNameAndType(DxColumn column, Version db // https://www.sqlite.org/autoinc.html if (column.IsAutoIncrement) { - column.ProviderDataType = SqliteTypes.sql_integer; + column.SetProviderDataType(ProviderType, SqliteTypes.sql_integer); } + return base.SqlInlineColumnNameAndType(column, dbVersion); } - protected override string SqlInlinePrimaryKeyAutoIncrementColumnConstraint() + protected override string SqlInlinePrimaryKeyAutoIncrementColumnConstraint(DxColumn column) { return "AUTOINCREMENT"; } diff --git a/src/DapperMatic/Providers/Sqlite/SqliteMethods.cs b/src/DapperMatic/Providers/Sqlite/SqliteMethods.cs index a72a274..357cf35 100644 --- a/src/DapperMatic/Providers/Sqlite/SqliteMethods.cs +++ b/src/DapperMatic/Providers/Sqlite/SqliteMethods.cs @@ -8,7 +8,7 @@ public partial class SqliteMethods : DatabaseMethodsBase, IDatabaseMethods { public override DbProviderType ProviderType => DbProviderType.Sqlite; - public override IProviderTypeMap ProviderTypeMap => SqliteProviderTypeMap.Instance.Value; + public override IDbProviderTypeMap ProviderTypeMap => SqliteProviderTypeMap.Instance.Value; protected override string DefaultSchema => ""; @@ -24,7 +24,7 @@ public override async Task GetDatabaseVersionAsync( const string sql = "SELECT sqlite_version()"; var versionString = await ExecuteScalarAsync(db, sql, tx: tx).ConfigureAwait(false) ?? ""; - return ProviderUtils.ExtractVersionFromVersionString(versionString); + return DbProviderUtils.ExtractVersionFromVersionString(versionString); } public override char[] QuoteChars => ['"']; diff --git a/src/DapperMatic/Providers/Sqlite/SqliteProviderTypeMap.cs b/src/DapperMatic/Providers/Sqlite/SqliteProviderTypeMap.cs index 750c725..d19d9b7 100644 --- a/src/DapperMatic/Providers/Sqlite/SqliteProviderTypeMap.cs +++ b/src/DapperMatic/Providers/Sqlite/SqliteProviderTypeMap.cs @@ -1,77 +1,330 @@ +using System.Collections; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Text.Json; +using System.Text.Json.Nodes; + namespace DapperMatic.Providers.Sqlite; -public sealed class SqliteProviderTypeMap : ProviderTypeMapBase +public sealed class SqliteProviderTypeMap : DbProviderTypeMapBase { internal static readonly Lazy Instance = new(() => new SqliteProviderTypeMap()); - private SqliteProviderTypeMap() : base() - { - } + private SqliteProviderTypeMap() + : base() { } protected override DbProviderType ProviderType => DbProviderType.Sqlite; + public override string SqTypeForStringLengthMax => "text"; + + public override string SqTypeForBinaryLengthMax => "blob"; + + public override string SqlTypeForJson => "text"; + /// /// IMPORTANT!! The order within an affinity group matters, as the first possible match will be used as the recommended sql type for a dotnet type /// - protected override ProviderSqlType[] ProviderSqlTypes => - [ - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_integer, formatWithPrecision: "integer({0})", - defaultPrecision: 11, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647), - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_int, aliasOf: "integer", formatWithPrecision: "int({0})", - defaultPrecision: 11, canUseToAutoIncrement: true, minValue: -2147483648, maxValue: 2147483647), - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_tinyint, formatWithPrecision: "tinyint({0})", - defaultPrecision: 4, canUseToAutoIncrement: true, minValue: -128, maxValue: 128), - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_smallint, formatWithPrecision: "smallint({0})", - defaultPrecision: 5, canUseToAutoIncrement: true, minValue: -32768, maxValue: 32767), - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_mediumint, formatWithPrecision: "mediumint({0})", - defaultPrecision: 7, canUseToAutoIncrement: true, minValue: -8388608, maxValue: 8388607), - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_bigint, formatWithPrecision: "bigint({0})", - defaultPrecision: 19, canUseToAutoIncrement: true, minValue: -9223372036854775808, - maxValue: 9223372036854775807), - new(ProviderSqlTypeAffinity.Integer, SqliteTypes.sql_unsigned_big_int, formatWithPrecision: "unsigned big int({0})", - defaultPrecision: 20, canUseToAutoIncrement: true, minValue: 0, maxValue: 18446744073709551615), - new(ProviderSqlTypeAffinity.Real, SqliteTypes.sql_real, formatWithPrecision: "real({0})", - defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, SqliteTypes.sql_double, formatWithPrecision: "double({0})", - defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, SqliteTypes.sql_float, formatWithPrecision: "float({0})", - defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, SqliteTypes.sql_numeric, formatWithPrecision: "numeric({0})", - defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Real, SqliteTypes.sql_decimal, formatWithPrecision: "decimal({0})", - defaultPrecision: 12, defaultScale: 2), - new(ProviderSqlTypeAffinity.Boolean, SqliteTypes.sql_bool, formatWithPrecision: "bool({0})", - defaultPrecision: 1), - new(ProviderSqlTypeAffinity.Boolean, SqliteTypes.sql_boolean, formatWithPrecision: "boolean({0})", - defaultPrecision: 1), - new(ProviderSqlTypeAffinity.DateTime, SqliteTypes.sql_date, formatWithPrecision: "date({0})", - defaultPrecision: 10, isDateOnly: true), - new(ProviderSqlTypeAffinity.DateTime, SqliteTypes.sql_datetime, formatWithPrecision: "datetime({0})", - defaultPrecision: 19), - new(ProviderSqlTypeAffinity.DateTime, SqliteTypes.sql_timestamp, formatWithPrecision: "timestamp({0})", - defaultPrecision: 19), - new(ProviderSqlTypeAffinity.DateTime, SqliteTypes.sql_time, formatWithPrecision: "time({0})", - defaultPrecision: 8, isTimeOnly: true), - new(ProviderSqlTypeAffinity.DateTime, SqliteTypes.sql_year, formatWithPrecision: "year({0})", - defaultPrecision: 4), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_char, formatWithPrecision: "char({0})", - defaultPrecision: 1), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_nchar, formatWithPrecision: "nchar({0})", - defaultPrecision: 1), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_varchar, formatWithPrecision: "varchar({0})", - defaultPrecision: 255), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_nvarchar, formatWithPrecision: "nvarchar({0})", - defaultPrecision: 255), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_varying_character, formatWithPrecision: "varying character({0})", - defaultPrecision: 255), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_native_character, formatWithPrecision: "native character({0})", - defaultPrecision: 255), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_text, formatWithPrecision: "text({0})", - defaultPrecision: 65535), - new(ProviderSqlTypeAffinity.Text, SqliteTypes.sql_clob, formatWithPrecision: "clob({0})", - defaultPrecision: 65535), - new(ProviderSqlTypeAffinity.Binary, SqliteTypes.sql_blob, formatWithPrecision: "blob({0})", - defaultPrecision: 65535), - ]; -} \ No newline at end of file + protected override DbProviderSqlType[] ProviderSqlTypes => + [ + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_integer, + formatWithPrecision: "integer({0})", + defaultPrecision: 11, + canUseToAutoIncrement: true, + minValue: -2147483648, + maxValue: 2147483647 + ), + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_int, + aliasOf: "integer", + formatWithPrecision: "int({0})", + defaultPrecision: 11, + canUseToAutoIncrement: true, + minValue: -2147483648, + maxValue: 2147483647 + ), + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_tinyint, + formatWithPrecision: "tinyint({0})", + defaultPrecision: 4, + canUseToAutoIncrement: true, + minValue: -128, + maxValue: 128 + ), + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_smallint, + formatWithPrecision: "smallint({0})", + defaultPrecision: 5, + canUseToAutoIncrement: true, + minValue: -32768, + maxValue: 32767 + ), + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_mediumint, + formatWithPrecision: "mediumint({0})", + defaultPrecision: 7, + canUseToAutoIncrement: true, + minValue: -8388608, + maxValue: 8388607 + ), + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_bigint, + formatWithPrecision: "bigint({0})", + defaultPrecision: 19, + canUseToAutoIncrement: true, + minValue: -9223372036854775808, + maxValue: 9223372036854775807 + ), + new( + DbProviderSqlTypeAffinity.Integer, + SqliteTypes.sql_unsigned_big_int, + formatWithPrecision: "unsigned big int({0})", + defaultPrecision: 20, + canUseToAutoIncrement: true, + minValue: 0, + maxValue: 18446744073709551615 + ), + new( + DbProviderSqlTypeAffinity.Real, + SqliteTypes.sql_real, + formatWithPrecision: "real({0})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + SqliteTypes.sql_double, + formatWithPrecision: "double({0})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + SqliteTypes.sql_float, + formatWithPrecision: "float({0})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + SqliteTypes.sql_numeric, + formatWithPrecision: "numeric({0})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Real, + SqliteTypes.sql_decimal, + formatWithPrecision: "decimal({0})", + defaultPrecision: 12, + defaultScale: 2 + ), + new( + DbProviderSqlTypeAffinity.Boolean, + SqliteTypes.sql_bool, + formatWithPrecision: "bool({0})", + defaultPrecision: 1 + ), + new( + DbProviderSqlTypeAffinity.Boolean, + SqliteTypes.sql_boolean, + formatWithPrecision: "boolean({0})", + defaultPrecision: 1 + ), + new( + DbProviderSqlTypeAffinity.DateTime, + SqliteTypes.sql_date, + formatWithPrecision: "date({0})", + defaultPrecision: 10, + isDateOnly: true + ), + new( + DbProviderSqlTypeAffinity.DateTime, + SqliteTypes.sql_datetime, + formatWithPrecision: "datetime({0})", + defaultPrecision: 19 + ), + new( + DbProviderSqlTypeAffinity.DateTime, + SqliteTypes.sql_timestamp, + formatWithPrecision: "timestamp({0})", + defaultPrecision: 19 + ), + new( + DbProviderSqlTypeAffinity.DateTime, + SqliteTypes.sql_time, + formatWithPrecision: "time({0})", + defaultPrecision: 8, + isTimeOnly: true + ), + new( + DbProviderSqlTypeAffinity.DateTime, + SqliteTypes.sql_year, + formatWithPrecision: "year({0})", + defaultPrecision: 4 + ), + new( + DbProviderSqlTypeAffinity.Text, + SqliteTypes.sql_nvarchar, + formatWithLength: "nvarchar({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + SqliteTypes.sql_varchar, + formatWithLength: "varchar({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + SqliteTypes.sql_nchar, + formatWithLength: "nchar({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + SqliteTypes.sql_char, + formatWithLength: "char({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + SqliteTypes.sql_varying_character, + formatWithLength: "varying character({0})", + defaultLength: DefaultLength + ), + new( + DbProviderSqlTypeAffinity.Text, + SqliteTypes.sql_native_character, + formatWithLength: "native character({0})", + defaultLength: DefaultLength + ), + new(DbProviderSqlTypeAffinity.Text, SqliteTypes.sql_text), + new(DbProviderSqlTypeAffinity.Text, SqliteTypes.sql_clob), + new(DbProviderSqlTypeAffinity.Binary, SqliteTypes.sql_blob), + ]; + + protected override bool TryGetProviderSqlTypeMatchingDotnetTypeInternal( + DbProviderDotnetTypeDescriptor descriptor, + out DbProviderSqlType? providerSqlType + ) + { + providerSqlType = null; + + var dotnetType = descriptor.DotnetType; + + // handle well-known types first + providerSqlType = dotnetType.IsGenericType + ? null + : dotnetType switch + { + Type t when t == typeof(bool) => ProviderSqlTypeLookup[SqliteTypes.sql_boolean], + Type t when t == typeof(byte) => ProviderSqlTypeLookup[SqliteTypes.sql_smallint], + Type t when t == typeof(ReadOnlyMemory) + => ProviderSqlTypeLookup[SqliteTypes.sql_smallint], + Type t when t == typeof(sbyte) => ProviderSqlTypeLookup[SqliteTypes.sql_smallint], + Type t when t == typeof(short) => ProviderSqlTypeLookup[SqliteTypes.sql_smallint], + Type t when t == typeof(ushort) => ProviderSqlTypeLookup[SqliteTypes.sql_smallint], + Type t when t == typeof(int) => ProviderSqlTypeLookup[SqliteTypes.sql_int], + Type t when t == typeof(uint) => ProviderSqlTypeLookup[SqliteTypes.sql_int], + Type t when t == typeof(long) => ProviderSqlTypeLookup[SqliteTypes.sql_bigint], + Type t when t == typeof(ulong) => ProviderSqlTypeLookup[SqliteTypes.sql_bigint], + Type t when t == typeof(float) => ProviderSqlTypeLookup[SqliteTypes.sql_float], + Type t when t == typeof(double) => ProviderSqlTypeLookup[SqliteTypes.sql_double], + Type t when t == typeof(decimal) => ProviderSqlTypeLookup[SqliteTypes.sql_decimal], + Type t when t == typeof(char) => ProviderSqlTypeLookup[SqliteTypes.sql_varchar], + Type t when t == typeof(string) + => descriptor.Length.GetValueOrDefault(255) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(char[]) + => descriptor.Length.GetValueOrDefault(255) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(ReadOnlyMemory[]) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(Stream) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(TextReader) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(byte[]) => ProviderSqlTypeLookup[SqliteTypes.sql_blob], + Type t when t == typeof(object) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(object[]) + => descriptor.Length.GetValueOrDefault(int.MaxValue) < 8000 + ? ProviderSqlTypeLookup[SqliteTypes.sql_varchar] + : ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t when t == typeof(Guid) => ProviderSqlTypeLookup[SqliteTypes.sql_varchar], + Type t when t == typeof(DateTime) + => ProviderSqlTypeLookup[SqliteTypes.sql_datetime], + Type t when t == typeof(DateTimeOffset) + => ProviderSqlTypeLookup[SqliteTypes.sql_timestamp], + Type t when t == typeof(TimeSpan) => ProviderSqlTypeLookup[SqliteTypes.sql_bigint], + Type t when t == typeof(DateOnly) => ProviderSqlTypeLookup[SqliteTypes.sql_date], + Type t when t == typeof(TimeOnly) => ProviderSqlTypeLookup[SqliteTypes.sql_time], + Type t when t == typeof(BitArray) || t == typeof(BitVector32) + => ProviderSqlTypeLookup[SqliteTypes.sql_blob], + Type t + when t == typeof(ImmutableDictionary) + || t == typeof(Dictionary) + || t == typeof(IDictionary) + => ProviderSqlTypeLookup[SqliteTypes.sql_text], + Type t + when t == typeof(JsonNode) + || t == typeof(JsonObject) + || t == typeof(JsonArray) + || t == typeof(JsonValue) + || t == typeof(JsonDocument) + || t == typeof(JsonElement) + => ProviderSqlTypeLookup[SqliteTypes.sql_text], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle generic types + providerSqlType = !dotnetType.IsGenericType + ? null + : dotnetType.GetGenericTypeDefinition() switch + { + Type t + when t == typeof(Dictionary<,>) + || t == typeof(IDictionary<,>) + || t == typeof(List<>) + || t == typeof(IList<>) + || t == typeof(Collection<>) + || t == typeof(ICollection<>) + || t == typeof(IEnumerable<>) + => ProviderSqlTypeLookup[SqliteTypes.sql_text], + _ => null + }; + + if (providerSqlType != null) + return true; + + // handle POCO type + if (dotnetType.IsClass || dotnetType.IsInterface) + { + providerSqlType = ProviderSqlTypeLookup[SqliteTypes.sql_text]; + } + + return providerSqlType != null; + } +} diff --git a/src/DapperMatic/Providers/Sqlite/SqliteSqlParser.cs b/src/DapperMatic/Providers/Sqlite/SqliteSqlParser.cs index 6d612ea..febb8ab 100644 --- a/src/DapperMatic/Providers/Sqlite/SqliteSqlParser.cs +++ b/src/DapperMatic/Providers/Sqlite/SqliteSqlParser.cs @@ -135,10 +135,11 @@ thirdChild.Children[1] is SqlWordClause sw2 // if we don't recognize the column data type, we skip it if ( - !providerTypeMap.TryGetRecommendedDotnetTypeMatchingSqlType( + !providerTypeMap.TryGetDotnetTypeDescriptorMatchingFullSqlTypeName( columnDataType, - out var providerDataType - ) || !providerDataType.HasValue + out var dotnetTypeDescriptor + ) + || dotnetTypeDescriptor == null ) continue; @@ -146,8 +147,11 @@ out var providerDataType null, tableName, columnName, - providerDataType.Value.dotnetType, - columnDataType, + dotnetTypeDescriptor.DotnetType, + new Dictionary + { + { DbProviderType.Sqlite, columnDataType } + }, length, precision, scale @@ -196,7 +200,7 @@ out var providerDataType // add the default constraint to the table var defaultConstraintName = inlineConstraintName - ?? ProviderUtils.GenerateDefaultConstraintName( + ?? DbProviderUtils.GenerateDefaultConstraintName( tableName, columnName ); @@ -218,7 +222,7 @@ out var providerDataType // add the default constraint to the table var uniqueConstraintName = inlineConstraintName - ?? ProviderUtils.GenerateUniqueConstraintName( + ?? DbProviderUtils.GenerateUniqueConstraintName( tableName, columnName ); @@ -247,7 +251,7 @@ [new DxOrderedColumn(column.ColumnName)] // add the default constraint to the table var checkConstraintName = inlineConstraintName - ?? ProviderUtils.GenerateCheckConstraintName( + ?? DbProviderUtils.GenerateCheckConstraintName( tableName, columnName ); @@ -269,7 +273,7 @@ [new DxOrderedColumn(column.ColumnName)] // add the default constraint to the table var pkConstraintName = inlineConstraintName - ?? ProviderUtils.GeneratePrimaryKeyConstraintName( + ?? DbProviderUtils.GeneratePrimaryKeyConstraintName( tableName, columnName ); @@ -323,7 +327,7 @@ [new DxOrderedColumn(column.ColumnName, columnOrder)] var constraintName = inlineConstraintName - ?? ProviderUtils.GenerateForeignKeyConstraintName( + ?? DbProviderUtils.GenerateForeignKeyConstraintName( tableName, columnName, referencedTableName, @@ -433,7 +437,7 @@ [new DxOrderedColumn(referenceColumnName)] null, tableName, inlineConstraintName - ?? ProviderUtils.GeneratePrimaryKeyConstraintName( + ?? DbProviderUtils.GeneratePrimaryKeyConstraintName( tableName, pkColumnNames ), @@ -472,7 +476,7 @@ [new DxOrderedColumn(referenceColumnName)] null, tableName, inlineConstraintName - ?? ProviderUtils.GenerateUniqueConstraintName( + ?? DbProviderUtils.GenerateUniqueConstraintName( tableName, ucColumnNames ), @@ -502,7 +506,7 @@ [new DxOrderedColumn(referenceColumnName)] // add the default constraint to the table var checkConstraintName = inlineConstraintName - ?? ProviderUtils.GenerateCheckConstraintName( + ?? DbProviderUtils.GenerateCheckConstraintName( tableName, table.CheckConstraints.Count > 0 ? $"{table.CheckConstraints.Count}" @@ -564,7 +568,7 @@ [new DxOrderedColumn(referenceColumnName)] var constraintName = inlineConstraintName - ?? ProviderUtils.GenerateForeignKeyConstraintName( + ?? DbProviderUtils.GenerateForeignKeyConstraintName( tableName, fkSourceColumnNames, referencedTableName, diff --git a/tests/DapperMatic.Tests/DatabaseMethodsTests.Columns.cs b/tests/DapperMatic.Tests/DatabaseMethodsTests.Columns.cs index 56d6e7a..dffd37d 100644 --- a/tests/DapperMatic.Tests/DatabaseMethodsTests.Columns.cs +++ b/tests/DapperMatic.Tests/DatabaseMethodsTests.Columns.cs @@ -280,302 +280,5 @@ await db.CreateTableIfNotExistsAsync( } await db.DropTableIfExistsAsync(schemaName, tableName); - - // Output.WriteLine("Column Exists: {0}.{1}", tableName, columnName); - // exists = await db.DoesColumnExistAsync(schemaName, tableName, columnName); - // Assert.True(exists); - - // Output.WriteLine("Dropping columnName: {0}.{1}", tableName, columnName); - // await db.DropColumnIfExistsAsync(schemaName, tableName, columnName); - - // Output.WriteLine("Column Exists: {0}.{1}", tableName, columnName); - // exists = await db.DoesColumnExistAsync(schemaName, tableName, columnName); - // Assert.False(exists); - - // // try adding a columnName of all the supported types - // var columnCount = 1; - // var addColumns = new List - // { - // new(schemaName, tableName2, "intid" + columnCount++, typeof(int)), - // new( - // schemaName, - // tableName2, - // "intpkid" + columnCount++, - // typeof(int), - // isPrimaryKey: true, - // isAutoIncrement: supportsMultipleIdentityColumns ? true : false - // ), - // new(schemaName, tableName2, "intucid" + columnCount++, typeof(int), isUnique: true), - // new( - // schemaName, - // tableName2, - // "id" + columnCount++, - // typeof(int), - // isUnique: true, - // isIndexed: true - // ), - // new(schemaName, tableName2, "intixid" + columnCount++, typeof(int), isIndexed: true), - // new( - // schemaName, - // tableName2, - // "colWithFk" + columnCount++, - // typeof(int), - // isForeignKey: true, - // referencedTableName: tableName, - // referencedColumnName: "id", - // onDelete: DxForeignKeyAction.Cascade, - // onUpdate: DxForeignKeyAction.Cascade - // ), - // new( - // schemaName, - // tableName2, - // "createdDateColumn" + columnCount++, - // typeof(DateTime), - // defaultExpression: defaultDateTimeSql - // ), - // new( - // schemaName, - // tableName2, - // "newidColumn" + columnCount++, - // typeof(Guid), - // defaultExpression: defaultGuidSql - // ), - // new(schemaName, tableName2, "bigintColumn" + columnCount++, typeof(long)), - // new(schemaName, tableName2, "binaryColumn" + columnCount++, typeof(byte[])), - // new(schemaName, tableName2, "bitColumn" + columnCount++, typeof(bool)), - // new(schemaName, tableName2, "charColumn" + columnCount++, typeof(string), length: 10), - // new(schemaName, tableName2, "dateColumn" + columnCount++, typeof(DateTime)), - // new(schemaName, tableName2, "datetimeColumn" + columnCount++, typeof(DateTime)), - // new(schemaName, tableName2, "datetime2Column" + columnCount++, typeof(DateTime)), - // new( - // schemaName, - // tableName2, - // "datetimeoffsetColumn" + columnCount++, - // typeof(DateTimeOffset) - // ), - // new( - // schemaName, - // tableName2, - // "decimalColumn" + columnCount++, - // typeof(decimal), - // precision: 16, - // scale: 3 - // ), - // new( - // schemaName, - // tableName2, - // "decimalColumnWithPrecision" + columnCount++, - // typeof(decimal), - // precision: 10 - // ), - // new( - // schemaName, - // tableName2, - // "decimalColumnWithPrecisionAndScale" + columnCount++, - // typeof(decimal), - // precision: 10, - // scale: 5 - // ), - // new(schemaName, tableName2, "floatColumn" + columnCount++, typeof(double)), - // new(schemaName, tableName2, "imageColumn" + columnCount++, typeof(byte[])), - // new(schemaName, tableName2, "intColumn" + columnCount++, typeof(int)), - // new(schemaName, tableName2, "moneyColumn" + columnCount++, typeof(decimal)), - // new(schemaName, tableName2, "ncharColumn" + columnCount++, typeof(string), length: 10), - // new( - // schemaName, - // tableName2, - // "ntextColumn" + columnCount++, - // typeof(string), - // length: int.MaxValue - // ), - // new(schemaName, tableName2, "floatColumn2" + columnCount++, typeof(float)), - // new(schemaName, tableName2, "doubleColumn2" + columnCount++, typeof(double)), - // new(schemaName, tableName2, "guidArrayColumn" + columnCount++, typeof(Guid[])), - // new(schemaName, tableName2, "intArrayColumn" + columnCount++, typeof(int[])), - // new(schemaName, tableName2, "longArrayColumn" + columnCount++, typeof(long[])), - // new(schemaName, tableName2, "doubleArrayColumn" + columnCount++, typeof(double[])), - // new(schemaName, tableName2, "decimalArrayColumn" + columnCount++, typeof(decimal[])), - // new(schemaName, tableName2, "stringArrayColumn" + columnCount++, typeof(string[])), - // new( - // schemaName, - // tableName2, - // "stringDictionaryArrayColumn" + columnCount++, - // typeof(Dictionary) - // ), - // new( - // schemaName, - // tableName2, - // "objectDitionaryArrayColumn" + columnCount++, - // typeof(Dictionary) - // ) - // }; - // await db.DropTableIfExistsAsync(schemaName, tableName2); - // await db.CreateTableIfNotExistsAsync(schemaName, tableName2, [addColumns[0]]); - // foreach (var col in addColumns.Skip(1)) - // { - // await db.CreateColumnIfNotExistsAsync(col); - // var columns = await db.GetColumnsAsync(schemaName, tableName2); - // // immediately do a check to make sure column was created as expected - // var column = await db.GetColumnAsync(schemaName, tableName2, col.ColumnName); - // Assert.NotNull(column); - - // if (!string.IsNullOrWhiteSpace(schemaName) && db.SupportsSchemas()) - // { - // Assert.Equal(schemaName, column.SchemaName, true); - // } - - // try - // { - // Assert.Equal(col.IsIndexed, column.IsIndexed); - // Assert.Equal(col.IsUnique, column.IsUnique); - // Assert.Equal(col.IsPrimaryKey, column.IsPrimaryKey); - // Assert.Equal(col.IsAutoIncrement, column.IsAutoIncrement); - // Assert.Equal(col.IsNullable, column.IsNullable); - // Assert.Equal(col.IsForeignKey, column.IsForeignKey); - // if (col.IsForeignKey) - // { - // Assert.Equal(col.ReferencedTableName, column.ReferencedTableName, true); - // Assert.Equal(col.ReferencedColumnName, column.ReferencedColumnName, true); - // Assert.Equal(col.OnDelete, column.OnDelete); - // Assert.Equal(col.OnUpdate, column.OnUpdate); - // } - // Assert.Equal(col.DotnetType, column.DotnetType); - // Assert.Equal(col.Length, column.Length); - // Assert.Equal(col.Precision, column.Precision); - // Assert.Equal(col.Scale ?? 0, column.Scale ?? 0); - // } - // catch (Exception ex) - // { - // Output.WriteLine("Error validating column {0}: {1}", col.ColumnName, ex.Message); - // column = await db.GetColumnAsync(schemaName, tableName2, col.ColumnName); - // } - - // Assert.NotNull(column?.ProviderDataType); - // Assert.NotEmpty(column.ProviderDataType); - // if (!string.IsNullOrWhiteSpace(col.ProviderDataType)) - // { - // if ( - // !col.ProviderDataType.Equals( - // column.ProviderDataType, - // StringComparison.OrdinalIgnoreCase - // ) - // ) - // { - // // then we want to make sure that the new provider data type in the database is more complete than the one we provided - // // sometimes, if you tell a database to create a column with a type of "decimal", it will actually create it as "decimal(11)" or something similar - // // in our case here, too, when creating a numeric(10, 5) column, the database might create it as decimal(10, 5) - // // so we CAN'T just compare the two strings directly - // // Assert.True(col.ProviderDataType.Length < column.ProviderDataType.Length); - - // // sometimes, it's tricky to know what the database will do, so we just want to make sure that the database type is at least as specific as the one we provided - // if (col.Length.HasValue) - // Assert.Equal(col.Length, column.Length); - // if (col.Precision.HasValue) - // Assert.Equal(col.Precision, column.Precision); - // if (col.Scale.HasValue) - // Assert.Equal(col.Scale, column.Scale); - // } - // } - // } - - // var actualColumns = await db.GetColumnsAsync(schemaName, tableName2); - // Output.WriteLine(JsonConvert.SerializeObject(actualColumns, Formatting.Indented)); - // var columnNames = await db.GetColumnNamesAsync(schemaName, tableName2); - // var expectedColumnNames = addColumns - // .OrderBy(c => c.ColumnName.ToLowerInvariant()) - // .Select(c => c.ColumnName.ToLowerInvariant()) - // .ToArray(); - // var actualColumnNames = columnNames - // .OrderBy(s => s.ToLowerInvariant()) - // .Select(s => s.ToLowerInvariant()) - // .ToArray(); - // Output.WriteLine("Expected columns: {0}", string.Join(", ", expectedColumnNames)); - // Output.WriteLine("Actual columns: {0}", string.Join(", ", actualColumnNames)); - // Output.WriteLine("Expected columns count: {0}", expectedColumnNames.Length); - // Output.WriteLine("Actual columns count: {0}", actualColumnNames.Length); - // Output.WriteLine( - // "Expected not in actual: {0}", - // string.Join(", ", expectedColumnNames.Except(actualColumnNames)) - // ); - // Output.WriteLine( - // "Actual not in expected: {0}", - // string.Join(", ", actualColumnNames.Except(expectedColumnNames)) - // ); - // Assert.Equal(expectedColumnNames.Length, actualColumnNames.Length); - // // Assert.Same(expectedColumnNames, actualColumnNames); - - // // validate that: - // // - all columns are of the expected types - // // - all indexes are created correctly - // // - all foreign keys are created correctly - // // - all default values are set correctly - // // - all column lengths are set correctly - // // - all column scales are set correctly - // // - all column precision is set correctly - // // - all columns are nullable or not nullable as specified - // // - all columns are unique or not unique as specified - // // - all columns are indexed or not indexed as specified - // // - all columns are foreign key or not foreign key as specified - // var table = await db.GetTableAsync(schemaName, tableName2); - // Assert.NotNull(table); - - // foreach (var column in table.Columns) - // { - // var originalColumn = addColumns.SingleOrDefault(c => - // c.ColumnName.Equals(column.ColumnName, StringComparison.OrdinalIgnoreCase) - // ); - // Assert.NotNull(originalColumn); - // } - - // // general count tests - // // some providers like MySQL create unique constraints for unique indexes, and vice-versa, so we can't just count the unique indexes - // Assert.Equal( - // addColumns.Count(c => !c.IsIndexed && c.IsUnique), - // dbType == DbProviderType.MySql - // ? table.UniqueConstraints.Count / 2 - // : table.UniqueConstraints.Count - // ); - // Assert.Equal( - // addColumns.Count(c => c.IsIndexed && !c.IsUnique), - // table.Indexes.Count(c => !c.IsUnique) - // ); - // var expectedUniqueIndexes = addColumns.Where(c => c.IsIndexed && c.IsUnique).ToArray(); - // var actualUniqueIndexes = table.Indexes.Where(c => c.IsUnique).ToArray(); - // Assert.Equal( - // expectedUniqueIndexes.Length, - // dbType == DbProviderType.MySql - // ? actualUniqueIndexes.Length / 2 - // : actualUniqueIndexes.Length - // ); - // Assert.Equal(addColumns.Count(c => c.IsForeignKey), table.ForeignKeyConstraints.Count()); - // Assert.Equal( - // addColumns.Count(c => c.DefaultExpression != null), - // table.DefaultConstraints.Count() - // ); - // Assert.Equal( - // addColumns.Count(c => c.CheckExpression != null), - // table.CheckConstraints.Count() - // ); - // Assert.Equal(addColumns.Count(c => c.IsNullable), table.Columns.Count(c => c.IsNullable)); - // Assert.Equal( - // addColumns.Count(c => c.IsPrimaryKey && c.IsAutoIncrement), - // table.Columns.Count(c => c.IsPrimaryKey && c.IsAutoIncrement) - // ); - // Assert.Equal(addColumns.Count(c => c.IsUnique), table.Columns.Count(c => c.IsUnique)); - - // var indexedColumnsExpected = addColumns.Where(c => c.IsIndexed).ToArray(); - // var uniqueColumnsNonIndexed = addColumns.Where(c => c.IsUnique && !c.IsIndexed).ToArray(); - - // var indexedColumnsActual = table.Columns.Where(c => c.IsIndexed).ToArray(); - - // Assert.Equal( - // dbType == DbProviderType.MySql - // ? indexedColumnsExpected.Length + uniqueColumnsNonIndexed.Length - // : indexedColumnsExpected.Length, - // indexedColumnsActual.Length - // ); - - // await db.DropTableIfExistsAsync(schemaName, tableName2); - // await db.DropTableIfExistsAsync(schemaName, tableName); } } diff --git a/tests/DapperMatic.Tests/DatabaseMethodsTests.DefaultConstraints.cs b/tests/DapperMatic.Tests/DatabaseMethodsTests.DefaultConstraints.cs index 2a4965b..1ebb890 100644 --- a/tests/DapperMatic.Tests/DatabaseMethodsTests.DefaultConstraints.cs +++ b/tests/DapperMatic.Tests/DatabaseMethodsTests.DefaultConstraints.cs @@ -24,7 +24,7 @@ [new DxColumn(schemaName, testTableName, testColumnName, typeof(int))] ); // in MySQL, default constraints are not named, so this MUST use the ProviderUtils method which is what DapperMatic uses internally - var constraintName = ProviderUtils.GenerateDefaultConstraintName( + var constraintName = DbProviderUtils.GenerateDefaultConstraintName( testTableName, testColumnName ); diff --git a/tests/DapperMatic.Tests/DatabaseMethodsTests.TableFactory.cs b/tests/DapperMatic.Tests/DatabaseMethodsTests.TableFactory.cs new file mode 100644 index 0000000..97afec5 --- /dev/null +++ b/tests/DapperMatic.Tests/DatabaseMethodsTests.TableFactory.cs @@ -0,0 +1,179 @@ +using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Dapper; +using DapperMatic.DataAnnotations; +using DapperMatic.Models; +using DapperMatic.Providers; +using Microsoft.Data.SqlClient.DataClassification; + +namespace DapperMatic.Tests; + +public abstract partial class DatabaseMethodsTests +{ + [Theory] + [InlineData(typeof(TestDao1))] + [InlineData(typeof(TestDao2))] + [InlineData(typeof(TestDao3))] + [InlineData(typeof(TestTable4))] + protected virtual async Task Can_create_tables_from_model_classes_async(Type type) + { + var tableDef = DxTableFactory.GetTable(type); + + using var db = await OpenConnectionAsync(); + + if (!string.IsNullOrWhiteSpace(tableDef.SchemaName)) + { + await db.CreateSchemaIfNotExistsAsync(tableDef.SchemaName); + } + + await db.CreateTableIfNotExistsAsync(tableDef); + + var tableExists = await db.DoesTableExistAsync(tableDef.SchemaName, tableDef.TableName); + Assert.True(tableExists); + + var dropped = await db.DropTableIfExistsAsync(tableDef.SchemaName, tableDef.TableName); + Assert.True(dropped); + } +} + +[Table("TestTable1")] +public class TestDao1 +{ + [Key] + public Guid Id { get; set; } +} + +[Table("TestTable2", Schema = "my_app")] +public class TestDao2 +{ + [Key] + public Guid Id { get; set; } +} + +[DxTable("TestTable3")] +public class TestDao3 +{ + [DxPrimaryKeyConstraint] + public Guid Id { get; set; } +} + +[DxPrimaryKeyConstraint([nameof(TestTable4.Id)])] +public class TestTable4 +{ + public Guid Id { get; set; } + + // create column of all supported types + public string StringColumn { get; set; } = null!; + public int IntColumn { get; set; } + public long LongColumn { get; set; } + public short ShortColumn { get; set; } + public byte ByteColumn { get; set; } + public decimal DecimalColumn { get; set; } + public double DoubleColumn { get; set; } + public float FloatColumn { get; set; } + public bool BoolColumn { get; set; } + public DateTime DateTimeColumn { get; set; } + public DateTimeOffset DateTimeOffsetColumn { get; set; } + public TimeSpan TimeSpanColumn { get; set; } + public byte[] ByteArrayColumn { get; set; } = null!; + public Guid GuidColumn { get; set; } + public char CharColumn { get; set; } + public char[] CharArrayColumn { get; set; } = null!; + public object ObjectColumn { get; set; } = null!; + + // create column of all supported nullable types + public string? NullableStringColumn { get; set; } + public int? NullableIntColumn { get; set; } + public long? NullableLongColumn { get; set; } + public short? NullableShortColumn { get; set; } + public byte? NullableByteColumn { get; set; } + public decimal? NullableDecimalColumn { get; set; } + public double? NullableDoubleColumn { get; set; } + public float? NullableFloatColumn { get; set; } + public bool? NullableBoolColumn { get; set; } + public DateTime? NullableDateTimeColumn { get; set; } + public DateTimeOffset? NullableDateTimeOffsetColumn { get; set; } + public TimeSpan? NullableTimeSpanColumn { get; set; } + public byte[]? NullableByteArrayColumn { get; set; } + public Guid? NullableGuidColumn { get; set; } + public char? NullableCharColumn { get; set; } + public char[]? NullableCharArrayColumn { get; set; } + public object? NullableObjectColumn { get; set; } + + // create columns of all enumerable types + public IDictionary IDictionaryColumn { get; set; } = null!; + public IDictionary? NullableIDictionaryColumn { get; set; } + public Dictionary DictionaryColumn { get; set; } = null!; + public Dictionary? NullableDictionaryColumn { get; set; } + public IDictionary IObjectDictionaryColumn { get; set; } = null!; + public IDictionary? NullableIObjectDictionaryColumn { get; set; } + public Dictionary ObjectDictionaryColumn { get; set; } = null!; + public Dictionary? NullableObjectDictionaryColumn { get; set; } + public IList IListColumn { get; set; } = null!; + public IList? NullableIListColumn { get; set; } + public List ListColumn { get; set; } = null!; + public List? NullableListColumn { get; set; } + public ICollection ICollectionColumn { get; set; } = null!; + public ICollection? NullableICollectionColumn { get; set; } + public Collection CollectionColumn { get; set; } = null!; + public Collection? NullableCollectionColumn { get; set; } + public IEnumerable IEnumerableColumn { get; set; } = null!; + public IEnumerable? NullableIEnumerableColumn { get; set; } + + // create columns of arrays + public string[] StringArrayColumn { get; set; } = null!; + public string[]? NullableStringArrayColumn { get; set; } + public int[] IntArrayColumn { get; set; } = null!; + public int[]? NullableIntArrayColumn { get; set; } + public long[] LongArrayColumn { get; set; } = null!; + public long[]? NullableLongArrayColumn { get; set; } + public Guid[] GuidArrayColumn { get; set; } = null!; + public Guid[]? NullableGuidArrayColumn { get; set; } + + // create columns of enums, structs and classes + public TestEnum EnumColumn { get; set; } + public TestEnum? NullableEnumColumn { get; set; } + public TestStruct StructColumn { get; set; } + public TestStruct? NullableStructColumn { get; set; } + public TestClass ClassColumn { get; set; } = null!; + public TestClass? NullableClassColumn { get; set; } + public TestInterface InterfaceColumn { get; set; } = null!; + public TestInterface? NullableInterfaceColumn { get; set; } + public TestAbstractClass AbstractClassColumn { get; set; } = null!; + public TestAbstractClass? NullableAbstractClassColumn { get; set; } + public TestConcreteClass ConcreteClass { get; set; } = null!; + public TestConcreteClass? NullableConcreteClass { get; set; } +} + +public enum TestEnum +{ + Value1, + Value2, + Value3 +} + +public struct TestStruct +{ + public int Value { get; set; } +} + +public class TestClass +{ + public int Value { get; set; } +} + +public interface TestInterface +{ + int Value { get; set; } +} + +public abstract class TestAbstractClass +{ + public int Value { get; set; } +} + +public class TestConcreteClass : TestAbstractClass +{ + public int Value2 { get; set; } +} diff --git a/tests/DapperMatic.Tests/DatabaseMethodsTests.Types.cs b/tests/DapperMatic.Tests/DatabaseMethodsTests.Types.cs index e6b609a..5735d1f 100644 --- a/tests/DapperMatic.Tests/DatabaseMethodsTests.Types.cs +++ b/tests/DapperMatic.Tests/DatabaseMethodsTests.Types.cs @@ -3,8 +3,8 @@ namespace DapperMatic.Tests; public abstract partial class DatabaseMethodsTests -{ - private static Type[] GetSupportedTypes(IProviderTypeMap dbTypeMap) +{ + private static Type[] GetSupportedTypes(IDbProviderTypeMap dbTypeMap) { // Type[] supportedTypes = dbTypeMap // .GetProviderSqlTypes() @@ -25,24 +25,31 @@ private static Type[] GetSupportedTypes(IProviderTypeMap dbTypeMap) // }) // .Distinct() // .ToArray(); - + Type[] typesToSupport = [ + typeof(bool), typeof(byte), + typeof(sbyte), typeof(short), typeof(int), typeof(long), - typeof(bool), typeof(float), typeof(double), typeof(decimal), + typeof(char), + typeof(string), + typeof(char[]), + typeof(ReadOnlyMemory[]), + typeof(Stream), + typeof(Guid), typeof(DateTime), typeof(DateTimeOffset), typeof(TimeSpan), + typeof(DateOnly), + typeof(TimeOnly), typeof(byte[]), typeof(object), - typeof(string), - typeof(Guid), // generic definitions typeof(IDictionary<,>), typeof(Dictionary<,>), @@ -75,7 +82,7 @@ private static Type[] GetSupportedTypes(IProviderTypeMap dbTypeMap) // custom classes typeof(TestClassDao) ]; - + return typesToSupport; } @@ -91,9 +98,8 @@ protected virtual async Task Provider_type_map_supports_all_desired_dotnet_types var dbTypeMap = db.GetProviderTypeMap(); foreach (var desiredType in GetSupportedTypes(dbTypeMap)) { - var exists = dbTypeMap.TryGetRecommendedSqlTypeMatchingDotnetType( - desiredType, - null, null, null, null, + var exists = dbTypeMap.TryGetProviderSqlTypeMatchingDotnetType( + new DbProviderDotnetTypeDescriptor(desiredType), out var sqlType ); diff --git a/tests/DapperMatic.Tests/ExtensionMethodTests.cs b/tests/DapperMatic.Tests/ExtensionMethodTests.cs new file mode 100644 index 0000000..d5eed47 --- /dev/null +++ b/tests/DapperMatic.Tests/ExtensionMethodTests.cs @@ -0,0 +1,44 @@ +namespace DapperMatic.Tests; + +public class ExtensionMethodTests +{ + // tes the GetFriendlyName method + [Theory] + [InlineData(typeof(bool), "Boolean")] + [InlineData(typeof(List), "List")] + [InlineData( + typeof(IEnumerable>>), + "IEnumerable>>" + )] + public void TestGetFriendlyName(Type input, string expected) + { + var actual = input.GetFriendlyName(); + Assert.Equal(expected, actual); + } + + // test the ToAlpha method + [Theory] + [InlineData("abc", "abc")] + [InlineData("abc123", "abc")] + [InlineData("abc123def", "abcdef")] + [InlineData("abc123def456", "abcdef")] + [InlineData("abc (&__-1234)123def456ghi", "abcdefghi")] + [InlineData("abc (&__-1234)123def456ghi", "abc(&__)defghi", "_&()")] + public void TestToAlpha(string input, string expected, string additionalAllowedCharacters = "") + { + var actual = input.ToAlpha(additionalAllowedCharacters); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("char", "char")] + [InlineData("decimal(10,2)", "decimal")] + [InlineData("abc(12,2) dd aa", "abc dd aa")] + [InlineData("abc ( 12 ,2 ) dd aa", "abc dd aa")] + [InlineData(" nvarchar ( 255 ) ", "nvarchar")] + public void TestDiscardLengthPrecisionAndScaleFromSqlTypeName(string input, string expected) + { + var actual = input.DiscardLengthPrecisionAndScaleFromSqlTypeName(); + Assert.Equal(expected, actual); + } +}