From 4b3dcf5f308a85617ed6acc10081c6aa58a4018d Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Sun, 25 Aug 2024 15:28:00 +0800 Subject: [PATCH] Improvements to migrations (#256) * Improve the migrations. - Missing index sort order in model and sql - When altering a column to non-nullable, have an UPDATE to make any NULL values the default value * Changes to comments produce no SQL output as we don't handle comments --- .../Migrations/JetMigrationsSqlGenerator.cs | 610 ++++++++++-------- .../Operations/JetCreateDatabaseOperation.cs | 2 +- .../Internal/JetDatabaseModelFactory.cs | 14 +- .../Storage/Internal/JetDatabaseCreator.cs | 2 +- .../GreenTests/ace_2010_odbc_x86.txt | 19 + .../GreenTests/ace_2010_oledb_x86.txt | 19 + .../Migrations/MigrationsJetTest.cs | 290 +++------ 7 files changed, 484 insertions(+), 472 deletions(-) diff --git a/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs b/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs index 6945cdc0..b88acd31 100644 --- a/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs +++ b/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs @@ -19,6 +19,8 @@ using System.Text; using EntityFrameworkCore.Jet.Update.Internal; using Microsoft.EntityFrameworkCore.Update; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Migrations @@ -36,29 +38,18 @@ namespace Microsoft.EntityFrameworkCore.Migrations /// public class JetMigrationsSqlGenerator : MigrationsSqlGenerator { - private readonly IMigrationsAnnotationProvider _migrationsAnnotations; - private readonly IJetOptions _options; - private IReadOnlyList _operations = null!; - private RelationalTypeMapping _stringTypeMapping; private readonly ICommandBatchPreparer _commandBatchPreparer; /// /// Creates a new instance. /// /// Parameter object containing dependencies for this service. - /// Provider-specific Migrations annotations to use. - /// Provider-specific options. /// The command batch preparer. public JetMigrationsSqlGenerator( - [NotNull] MigrationsSqlGeneratorDependencies dependencies, - [NotNull] IMigrationsAnnotationProvider migrationsAnnotations, - [NotNull] IJetOptions options, + MigrationsSqlGeneratorDependencies dependencies, ICommandBatchPreparer commandBatchPreparer) : base(dependencies) { - _migrationsAnnotations = migrationsAnnotations; - _options = options; - _stringTypeMapping = dependencies.TypeMappingSource.FindMapping(typeof(string))!; _commandBatchPreparer = commandBatchPreparer; } @@ -102,9 +93,6 @@ public override IReadOnlyList Generate( /// The command builder to use to build the commands. protected override void Generate(MigrationOperation operation, IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - switch (operation) { case JetCreateDatabaseOperation createDatabaseOperation: @@ -150,6 +138,54 @@ protected override void Generate( } } + /// + /// Builds commands for the given by making calls on the given + /// . + /// + /// The operation. + /// The target model which may be if the operations exist without a model. + /// The command builder to use to build the commands. + /// Indicates whether or not to terminate the command after generating SQL for the operation. + protected override void Generate( + AddForeignKeyOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool terminate = true) + { + base.Generate(operation, model, builder, terminate: false); + + if (terminate) + { + builder + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); + } + } + + /// + /// Builds commands for the given by making calls on the given + /// . + /// + /// The operation. + /// The target model which may be if the operations exist without a model. + /// The command builder to use to build the commands. + /// Indicates whether or not to terminate the command after generating SQL for the operation. + protected override void Generate( + AddPrimaryKeyOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool terminate = true) + { + base.Generate(operation, model, builder, terminate: false); + + if (terminate) + { + builder + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); + } + } + /// /// Builds commands for the given /// by making calls on the given . @@ -162,14 +198,16 @@ protected override void Generate( IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); + if (operation[RelationalAnnotationNames.ColumnOrder] != operation.OldColumn[RelationalAnnotationNames.ColumnOrder]) + { + Dependencies.MigrationsLogger.ColumnOrderIgnoredWarning(operation); + } IEnumerable? indexesToRebuild = null; var column = model?.GetRelationalModel().FindTable(operation.Table, operation.Schema) ?.Columns.FirstOrDefault(c => c.Name == operation.Name); - if (operation.ComputedColumnSql != null) + if (operation.ComputedColumnSql != operation.OldColumn.ComputedColumnSql) { var dropColumnOperation = new DropColumnOperation { @@ -190,13 +228,18 @@ protected override void Generate( ClrType = operation.ClrType, ColumnType = operation.ColumnType, IsUnicode = operation.IsUnicode, + IsFixedLength = operation.IsFixedLength, MaxLength = operation.MaxLength, + Precision = operation.Precision, + Scale = operation.Scale, IsRowVersion = operation.IsRowVersion, IsNullable = operation.IsNullable, DefaultValue = operation.DefaultValue, DefaultValueSql = operation.DefaultValueSql, ComputedColumnSql = operation.ComputedColumnSql, - IsFixedLength = operation.IsFixedLength + IsStored = operation.IsStored, + Comment = operation.Comment, + Collation = operation.Collation }; addColumnOperation.AddAnnotations(operation.GetAnnotations()); @@ -205,124 +248,169 @@ protected override void Generate( DropIndexes(indexesToRebuild, builder); Generate(dropColumnOperation, model, builder, terminate: false); builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - Generate(addColumnOperation, model, builder, terminate: false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + Generate(addColumnOperation, model, builder); CreateIndexes(indexesToRebuild, builder); builder.EndCommand(); return; } + var columnType = operation.ColumnType + ?? GetColumnType( + operation.Schema, + operation.Table, + operation.Name, + operation, + model); + var narrowed = false; - if (IsOldColumnSupported(model)) + var oldColumnSupported = IsOldColumnSupported(model); + + if (oldColumnSupported) { if (IsIdentity(operation) != IsIdentity(operation.OldColumn)) { throw new InvalidOperationException(JetStrings.AlterIdentityColumn); } - var type = operation.ColumnType - ?? GetColumnType( - operation.Schema, - operation.Table, - operation.Name, - operation, - model); var oldType = operation.OldColumn.ColumnType - ?? GetColumnType( - operation.Schema, - operation.Table, - operation.Name, - operation.OldColumn, - model); - narrowed = type != oldType || !operation.IsNullable && operation.OldColumn.IsNullable; + ?? GetColumnType( + operation.Schema, + operation.Table, + operation.Name, + operation.OldColumn, + model); + narrowed = columnType != oldType + || operation.Collation != operation.OldColumn.Collation + || operation is { IsNullable: false, OldColumn.IsNullable: true }; } if (narrowed) { - indexesToRebuild = GetIndexesToRebuild(column, operation) - .ToList(); + indexesToRebuild = GetIndexesToRebuild(column, operation).ToList(); DropIndexes(indexesToRebuild, builder); } - DropDefaultConstraint(operation.Table, operation.Name, builder); - - builder - .Append("ALTER TABLE ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table)) - .Append(" ALTER COLUMN "); + var newAnnotations = operation.GetAnnotations().Where(a => a.Name != JetAnnotationNames.Identity); + var oldAnnotations = operation.OldColumn.GetAnnotations().Where(a => a.Name != JetAnnotationNames.Identity); + + var alterStatementNeeded = narrowed + || !oldColumnSupported + || operation.ClrType != operation.OldColumn.ClrType + || columnType != operation.OldColumn.ColumnType + || operation.IsUnicode != operation.OldColumn.IsUnicode + || operation.IsFixedLength != operation.OldColumn.IsFixedLength + || operation.MaxLength != operation.OldColumn.MaxLength + || operation.Precision != operation.OldColumn.Precision + || operation.Scale != operation.OldColumn.Scale + || operation.IsRowVersion != operation.OldColumn.IsRowVersion + || operation.IsNullable != operation.OldColumn.IsNullable + || operation.Collation != operation.OldColumn.Collation + || operation.DefaultValue != operation.OldColumn.DefaultValue + || HasDifferences(newAnnotations, oldAnnotations); + + var (oldDefaultValue, oldDefaultValueSql) = (operation.OldColumn.DefaultValue, operation.OldColumn.DefaultValueSql); + + if (alterStatementNeeded + || !Equals(operation.DefaultValue, oldDefaultValue) + || operation.DefaultValueSql != oldDefaultValueSql) + { + DropDefaultConstraint(operation.Table, operation.Name, builder); + (oldDefaultValue, oldDefaultValueSql) = (null, null); + } - // NB: DefaultValue, DefaultValueSql, and identity are handled elsewhere. Don't copy them here. - var definitionOperation = new AlterColumnOperation + // The column is being made non-nullable. Generate an update statement before doing that, to convert any existing null values to + // the default value + if (operation is { IsNullable: false, OldColumn.IsNullable: true } + && (operation.DefaultValueSql is not null || operation.DefaultValue is not null)) { - Schema = operation.Schema, - Table = operation.Table, - Name = operation.Name, - ClrType = operation.ClrType, - ColumnType = operation.ColumnType, - IsUnicode = operation.IsUnicode, - IsFixedLength = operation.IsFixedLength, - MaxLength = operation.MaxLength, - IsRowVersion = operation.IsRowVersion, - IsNullable = operation.IsNullable, - ComputedColumnSql = operation.ComputedColumnSql, - OldColumn = operation.OldColumn - }; - definitionOperation.AddAnnotations( - operation.GetAnnotations() - .Where( - a => a.Name != JetAnnotationNames.ValueGenerationStrategy - && a.Name != JetAnnotationNames.Identity)); + string defaultValueSql; + if (operation.DefaultValueSql is not null) + { + defaultValueSql = operation.DefaultValueSql; + } + else + { + Check.DebugAssert(operation.DefaultValue is not null, "operation.DefaultValue is not null"); - ColumnDefinition( - operation.Schema, - operation.Table, - operation.Name, - definitionOperation, - model, - builder); + var typeMapping = (columnType != null + ? Dependencies.TypeMappingSource.FindMapping(operation.DefaultValue.GetType(), columnType) + : null) + ?? Dependencies.TypeMappingSource.GetMappingForValue(operation.DefaultValue); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + defaultValueSql = typeMapping.GenerateSqlLiteral(operation.DefaultValue); + } - if (operation.DefaultValue != null - || operation.DefaultValueSql != null) - { - builder - .Append("ALTER TABLE ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table)) - .Append(" ADD"); - DefaultValue(operation.DefaultValue, operation.DefaultValueSql, operation.ColumnType, builder); - builder - .Append(" FOR ") + var updateBuilder = new StringBuilder() + .Append("UPDATE ") + .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema)) + .Append(" SET ") .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) - .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } + .Append(" = ") + .Append(defaultValueSql) + .Append(" WHERE ") + .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) + .Append(" IS NULL"); - // TODO: Implement comment/description support. (ADOX/DAO) - /* - if (operation.OldColumn.Comment != operation.Comment) - { - var dropDescription = operation.OldColumn.Comment != null; - if (dropDescription) + if (Options.HasFlag(MigrationsSqlGenerationOptions.Idempotent)) { - DropDescription( - builder, - operation.Schema, - operation.Table, - operation.Name); + builder + .Append("EXEC(N'") + .Append(updateBuilder.ToString().TrimEnd('\n', '\r', ';').Replace("'", "''")) + .Append("')"); } - - if (operation.Comment != null) + else { - AddDescription( - builder, operation.Comment, - operation.Schema, - operation.Table, - operation.Name, - omitSchemaVariable: dropDescription); + builder.Append(updateBuilder.ToString()); } + + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + if (alterStatementNeeded) + { + builder + .Append("ALTER TABLE ") + .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema)) + .Append(" ALTER COLUMN "); + + // NB: ComputedColumnSql, IsStored, DefaultValueSql, Comment, ValueGenerationStrategy, and Identity are + // handled elsewhere. Don't copy them here. + //JET: DefaultValue is part of the ALTER COLUMN. Not a separate ALTER + var definitionOperation = new AlterColumnOperation + { + Schema = operation.Schema, + Table = operation.Table, + Name = operation.Name, + ClrType = operation.ClrType, + ColumnType = operation.ColumnType, + IsUnicode = operation.IsUnicode, + IsFixedLength = operation.IsFixedLength, + MaxLength = operation.MaxLength, + Precision = operation.Precision, + Scale = operation.Scale, + IsRowVersion = operation.IsRowVersion, + IsNullable = operation.IsNullable, + Collation = operation.Collation, + OldColumn = operation.OldColumn, + DefaultValue = operation.DefaultValue, + DefaultValueSql = operation.DefaultValueSql + }; + definitionOperation.AddAnnotations( + operation.GetAnnotations().Where( + a => a.Name != JetAnnotationNames.ValueGenerationStrategy + && a.Name != JetAnnotationNames.Identity)); + + ColumnDefinition( + operation.Schema, + operation.Table, + operation.Name, + definitionOperation, + model, + builder); + + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } - */ if (narrowed) { @@ -344,9 +432,6 @@ protected override void Generate( IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - if (string.IsNullOrEmpty(operation.Table)) { throw new InvalidOperationException(JetStrings.IndexTableRequired); @@ -362,6 +447,29 @@ protected override void Generate( .EndCommand(); } + /// + /// Builds commands for the given by making calls on the given + /// . + /// + /// The operation. + /// The target model which may be if the operations exist without a model. + /// The command builder to use to build the commands. + /// Indicates whether or not to terminate the command after generating SQL for the operation. + protected override void Generate( + CreateTableOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool terminate = true) + { + base.Generate(operation, model, builder, terminate: false); + if (terminate) + { + builder + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); + } + } + /// /// Builds commands for the given /// by making calls on the given . @@ -374,9 +482,6 @@ protected override void Generate( IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - // CHECK: Rename table operations require extensions like ADOX or DAO. // A native way to do this would be to: // 1. CREATE TABLE `destination table` @@ -391,6 +496,30 @@ protected override void Generate( .EndCommand(); } + /// + /// Builds commands for the given by making calls on the given + /// . + /// + /// The operation. + /// The target model which may be if the operations exist without a model. + /// The command builder to use to build the commands. + /// Indicates whether or not to terminate the command after generating SQL for the operation. + protected override void Generate( + DropTableOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool terminate = true) + { + base.Generate(operation, model, builder, terminate: false); + + if (terminate) + { + builder + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); + } + } + /// /// Builds commands for the given by making calls on the given /// . @@ -405,41 +534,52 @@ protected override void Generate( MigrationCommandListBuilder builder, bool terminate = true) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - - builder.Append("CREATE "); - - if (operation.IsUnique) + base.Generate(operation, model, builder, terminate: false); + if (terminate) { - builder.Append("UNIQUE "); + builder + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); } + } - IndexTraits(operation, model, builder); - - builder - .Append("INDEX ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) - .Append(" ON ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table)) - .Append(" (") - .Append(ColumnList(operation.Columns)) - .Append(")"); - - if (!string.IsNullOrEmpty(operation.Filter)) + /// + /// Builds commands for the given by making calls on the given + /// . + /// + /// The operation. + /// The target model which may be if the operations exist without a model. + /// The command builder to use to build the commands. + /// Indicates whether or not to terminate the command after generating SQL for the operation. + protected override void Generate( + DropPrimaryKeyOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool terminate = true) + { + base.Generate(operation, model, builder, terminate: false); + if (terminate) { builder - .Append(" WITH ") - .Append(operation.Filter); + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); } + } - if (terminate) + /// + /// Generates a SQL fragment for extras (filter, included columns, options) of an index from a . + /// + /// The operation. + /// The target model which may be if the operations exist without a model. + /// The command builder to use to add the SQL fragment. + protected override void IndexOptions(MigrationOperation operation, IModel? model, MigrationCommandListBuilder builder) + { + if (operation is CreateIndexOperation createIndexOperation + && !string.IsNullOrEmpty(createIndexOperation.Filter)) { - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - EndStatement(builder); + builder + .Append(" WITH ") + .Append(createIndexOperation.Filter); } } @@ -451,22 +591,20 @@ protected override void Generate( /// The target model which may be null if the operations exist without a model. /// The command builder to use to build the commands. protected virtual void Generate( - [NotNull] JetCreateDatabaseOperation operation, - [CanBeNull] IModel? model, - [NotNull] MigrationCommandListBuilder builder) + JetCreateDatabaseOperation operation, + IModel? model, + MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - + var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); builder .Append("CREATE DATABASE ") - .Append(_stringTypeMapping.GenerateSqlLiteral(operation.Name)); + .Append(stringTypeMapping.GenerateSqlLiteral(operation.Name)); if (!string.IsNullOrEmpty(operation.Password)) { builder .Append(" PASSWORD ") - .Append(_stringTypeMapping.GenerateSqlLiteral(operation.Password)); + .Append(stringTypeMapping.GenerateSqlLiteral(operation.Password)); } builder @@ -504,17 +642,15 @@ private static string ExpandFileName(string fileName) /// The target model which may be null if the operations exist without a model. /// The command builder to use to build the commands. protected virtual void Generate( - [NotNull] JetDropDatabaseOperation operation, - [CanBeNull] IModel? model, - [NotNull] MigrationCommandListBuilder builder) + JetDropDatabaseOperation operation, + IModel? model, + MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - + var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); builder .Append("DROP DATABASE ") //.Append(ExpandFileName(operation.Name)) - .Append(_stringTypeMapping.GenerateSqlLiteral(operation.Name)) + .Append(stringTypeMapping.GenerateSqlLiteral(operation.Name)) .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) .EndCommand(suppressTransaction: true); } @@ -531,8 +667,6 @@ protected override void Generate( IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); } /// @@ -549,18 +683,14 @@ protected override void Generate( MigrationCommandListBuilder builder, bool terminate = true) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - - builder - .Append("ALTER TABLE ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table)) - .Append(" DROP CONSTRAINT ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)); + base.Generate(operation, model, builder, terminate: false); - builder - .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) - .EndCommand(); + if (terminate) + { + builder + .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator) + .EndCommand(); + } } /// @@ -572,14 +702,15 @@ protected override void Generate( /// The command builder to use to build the commands. /// Indicates whether or not to terminate the command after generating SQL for the operation. protected override void Generate( - [NotNull] DropIndexOperation operation, - [CanBeNull] IModel? model, - [NotNull] MigrationCommandListBuilder builder, + DropIndexOperation operation, + IModel? model, + MigrationCommandListBuilder builder, bool terminate = true) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - + if (string.IsNullOrEmpty(operation.Table)) + { + throw new InvalidOperationException(JetStrings.IndexTableRequired); + } builder .Append("DROP INDEX ") .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) @@ -608,9 +739,6 @@ protected override void Generate( MigrationCommandListBuilder builder, bool terminate = true) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - DropDefaultConstraint(operation.Table, operation.Name, builder); base.Generate(operation, model, builder, terminate: false); @@ -634,9 +762,6 @@ protected override void Generate( IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - builder.Append("ALTER TABLE ") .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table)) .Append(" RENAME COLUMN ") @@ -656,9 +781,6 @@ protected override void Generate( /// The command builder to use to build the commands. protected override void Generate(SqlOperation operation, IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - // TODO: Jet does not support batches and should never generate a "GO" in the first place. // So this code should do nothing. @@ -753,10 +875,6 @@ protected override void ColumnDefinition( IModel? model, MigrationCommandListBuilder builder) { - Check.NotEmpty(name, nameof(name)); - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - if (operation.ComputedColumnSql != null) { if (decimal.TryParse(operation.ComputedColumnSql, out decimal result)) @@ -787,11 +905,11 @@ protected override void ColumnDefinition( } protected override string? GetColumnType( - [CanBeNull] string? schema, - [NotNull] string table, - [NotNull] string name, - [NotNull] ColumnOperation operation, - [CanBeNull] IModel? model) + string? schema, + string table, + string name, + ColumnOperation operation, + IModel? model) { var storeType = operation.ColumnType; @@ -834,8 +952,6 @@ operation[JetAnnotationNames.Identity] is string identity && /// The command builder to use to add the SQL fragment. protected override void ForeignKeyAction(ReferentialAction referentialAction, MigrationCommandListBuilder builder) { - Check.NotNull(builder, nameof(builder)); - if (referentialAction == ReferentialAction.Restrict) { builder.Append("NO ACTION"); @@ -854,13 +970,11 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi /// Store/database type of the column. /// The command builder to use to add the SQL fragment. protected override void DefaultValue( - [CanBeNull] object? defaultValue, - [CanBeNull] string? defaultValueSql, - [CanBeNull] string? columnType, - [NotNull] MigrationCommandListBuilder builder) + object? defaultValue, + string? defaultValueSql, + string? columnType, + MigrationCommandListBuilder builder) { - Check.NotNull(builder, nameof(builder)); - if (defaultValueSql != null) { builder @@ -885,55 +999,6 @@ protected override void DefaultValue( } } - /// - /// Generates a SQL fragment for a foreign key constraint of an . - /// - /// The operation. - /// The target model which may be null if the operations exist without a model. - /// The command builder to use to add the SQL fragment. - protected override void ForeignKeyConstraint( - AddForeignKeyOperation operation, - IModel? model, - MigrationCommandListBuilder builder) - { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); - - if (operation.Name != null) - { - builder - .Append("CONSTRAINT ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) - .Append(" "); - } - - builder - .Append("FOREIGN KEY (") - .Append(ColumnList(operation.Columns)) - .Append(") REFERENCES ") - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.PrincipalTable)); - - if (operation.PrincipalColumns != null) - { - builder - .Append(" (") - .Append(ColumnList(operation.PrincipalColumns)) - .Append(")"); - } - - if (operation.OnUpdate != ReferentialAction.NoAction) - { - builder.Append(" ON UPDATE "); - ForeignKeyAction(operation.OnUpdate, builder); - } - - if (operation.OnDelete != ReferentialAction.NoAction) - { - builder.Append(" ON DELETE "); - ForeignKeyAction(operation.OnDelete, builder); - } - } - /// /// Generates a SQL fragment to drop default constraints for a column. /// @@ -941,14 +1006,10 @@ protected override void ForeignKeyConstraint( /// The column. /// The command builder to use to add the SQL fragment. protected virtual void DropDefaultConstraint( - [NotNull] string tableName, - [NotNull] string columnName, - [NotNull] MigrationCommandListBuilder builder) + string tableName, + string columnName, + MigrationCommandListBuilder builder) { - Check.NotEmpty(tableName, nameof(tableName)); - Check.NotEmpty(columnName, nameof(columnName)); - Check.NotNull(builder, nameof(builder)); - builder .Append("ALTER TABLE ") .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableName)) @@ -965,11 +1026,9 @@ protected virtual void DropDefaultConstraint( /// The operation which may require a rebuild. /// The list of indexes affected. protected virtual IEnumerable GetIndexesToRebuild( - [CanBeNull] IColumn? column, - [NotNull] MigrationOperation currentOperation) + IColumn? column, + MigrationOperation currentOperation) { - Check.NotNull(currentOperation, nameof(currentOperation)); - if (column == null) { yield break; @@ -1006,12 +1065,9 @@ protected virtual IEnumerable GetIndexesToRebuild( /// The indexes to drop. /// The command builder to use to build the commands. protected virtual void DropIndexes( - [NotNull] IEnumerable indexes, - [NotNull] MigrationCommandListBuilder builder) + IEnumerable indexes, + MigrationCommandListBuilder builder) { - Check.NotNull(indexes, nameof(indexes)); - Check.NotNull(builder, nameof(builder)); - foreach (var index in indexes) { var table = index.Table; @@ -1034,12 +1090,9 @@ protected virtual void DropIndexes( /// The indexes to create. /// The command builder to use to build the commands. protected virtual void CreateIndexes( - [NotNull] IEnumerable indexes, - [NotNull] MigrationCommandListBuilder builder) + IEnumerable indexes, + MigrationCommandListBuilder builder) { - Check.NotNull(indexes, nameof(indexes)); - Check.NotNull(builder, nameof(builder)); - foreach (var index in indexes) { Generate(CreateIndexOperation.CreateFrom(index), index.Table.Model.Model, builder, terminate: false); @@ -1057,18 +1110,33 @@ private static bool IsExplicitIdentityColumnType(string columnType) string.Equals("identity", columnType, StringComparison.OrdinalIgnoreCase) || string.Equals("autoincrement", columnType, StringComparison.OrdinalIgnoreCase); + private static bool HasDifferences(IEnumerable source, IEnumerable target) + { + var targetAnnotations = target.ToDictionary(a => a.Name); + + var count = 0; + foreach (var sourceAnnotation in source) + { + if (!targetAnnotations.TryGetValue(sourceAnnotation.Name, out var targetAnnotation) + || !Equals(sourceAnnotation.Value, targetAnnotation.Value)) + { + return true; + } + + count++; + } + + return count != targetAnnotations.Count; + } + #region Schemas not supported protected override void Generate(EnsureSchemaOperation operation, IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); } protected override void Generate(DropSchemaOperation operation, IModel? model, MigrationCommandListBuilder builder) { - Check.NotNull(operation, nameof(operation)); - Check.NotNull(builder, nameof(builder)); } #endregion diff --git a/src/EFCore.Jet/Migrations/Operations/JetCreateDatabaseOperation.cs b/src/EFCore.Jet/Migrations/Operations/JetCreateDatabaseOperation.cs index e68c9fb1..be86c811 100644 --- a/src/EFCore.Jet/Migrations/Operations/JetCreateDatabaseOperation.cs +++ b/src/EFCore.Jet/Migrations/Operations/JetCreateDatabaseOperation.cs @@ -13,7 +13,7 @@ public class JetCreateDatabaseOperation : MigrationOperation /// /// The name of the database. /// - public virtual string? Name { get; set; } = null!; + public virtual string Name { get; set; } = null!; public virtual string? Password { get; [param: CanBeNull] set; } } } diff --git a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs index f4b9f521..bb741b49 100644 --- a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs +++ b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; using EntityFrameworkCore.Jet.Internal; using JetBrains.Annotations; @@ -21,6 +20,7 @@ using EntityFrameworkCore.Jet.Utilities; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.Reflection; namespace EntityFrameworkCore.Jet.Scaffolding.Internal { @@ -500,11 +500,22 @@ private void GetIndexes(DbConnection connection, IReadOnlyList ta continue; } + string? filter = null; + if (!nullable) + { + filter = "DISALLOW NULL"; + } + + if (ignoresNulls) + { + filter = "IGNORE NULLS"; + } var index = new DatabaseIndex { Table = table, Name = indexName, IsUnique = isUnique, + Filter = filter }; _logger.IndexFound(indexName!, tableName!, index.IsUnique); @@ -535,6 +546,7 @@ private void GetIndexes(DbConnection connection, IReadOnlyList ta case DatabaseIndex index: index.Columns.Add(column); + index.IsDescending.Add(descending); break; } } diff --git a/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs b/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs index 44a4f0fc..5c4a3ade 100644 --- a/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs +++ b/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs @@ -120,7 +120,7 @@ private IReadOnlyList CreateCreateOperations() { new JetCreateDatabaseOperation { - Name = dataSource, + Name = dataSource!, Password = databasePassword } }); diff --git a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt index 9856afbc..20764277 100644 --- a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt +++ b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt @@ -8251,6 +8251,7 @@ EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_optiona EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_composite_with_name EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_int EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_string +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_with_name EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_required_primitive_collection_to_existing_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table @@ -8265,13 +8266,28 @@ EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_check EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_add_comment EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_add_identity EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_change_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_change_default EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_change_type +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required_with_composite_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required_with_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required_with_null_data +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_remove_comment EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_remove_identity EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_computed_column_add_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_index_change_sort_order EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_add_comment EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_add_comment_non_default_schema EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_change_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_remove_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Convert_json_entities_to_regular_owned +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Convert_regular_owned_entities_to_json +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Convert_string_column_to_a_json_column_containing_required_reference +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_index_descending +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_index_descending_mixed EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_schema +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_schema_dbo_is_ignored EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_table_no_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_table_with_comments @@ -8286,12 +8302,15 @@ EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.DeleteDataO EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.DeleteDataOperation_simple_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_check_constraint EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_column +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_column_primary_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_foreign_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_json_columns_from_existing_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_primary_key_int EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_primary_key_string EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_unique_constraint +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.InsertDataOperation EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Rename_column EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Rename_json_column EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.UpdateDataOperation_composite_key diff --git a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt index 94f0bd6a..a168299e 100644 --- a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt +++ b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt @@ -8323,6 +8323,7 @@ EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_optiona EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_composite_with_name EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_int EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_string +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_primary_key_with_name EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_required_primitive_collection_to_existing_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table @@ -8337,13 +8338,28 @@ EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_check EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_add_comment EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_add_identity EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_change_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_change_default EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_change_type +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required_with_composite_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required_with_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_make_required_with_null_data +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_remove_comment EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_column_remove_identity EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_computed_column_add_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_index_change_sort_order EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_add_comment EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_add_comment_non_default_schema EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_change_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Alter_table_remove_comment +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Convert_json_entities_to_regular_owned +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Convert_regular_owned_entities_to_json +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Convert_string_column_to_a_json_column_containing_required_reference +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_index_descending +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_index_descending_mixed EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_schema +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_schema_dbo_is_ignored EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_table_no_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Create_table_with_comments @@ -8358,12 +8374,15 @@ EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.DeleteDataO EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.DeleteDataOperation_simple_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_check_constraint EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_column +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_column_primary_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_foreign_key EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_index +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_json_columns_from_existing_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_primary_key_int EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_primary_key_string EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_table EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Drop_unique_constraint +EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.InsertDataOperation EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Rename_column EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.Rename_json_column EntityFrameworkCore.Jet.FunctionalTests.Migrations.MigrationsJetTest.UpdateDataOperation_composite_key diff --git a/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs b/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs index 3d423873..d421f4ca 100644 --- a/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs @@ -238,12 +238,7 @@ public override async Task Alter_table_remove_comment() await base.Alter_table_remove_comment(); AssertSql( - """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; -"""); + ); } public override async Task Rename_table() @@ -339,9 +334,9 @@ await Test( AssertSql( """ -CREATE TABLE [dbo].[People] ( - [Id] int NOT NULL IDENTITY, - CONSTRAINT [PK_People] PRIMARY KEY ([Id]) +CREATE TABLE `People` ( + `Id` counter NOT NULL, + CONSTRAINT `PK_People` PRIMARY KEY (`Id`) ); """); } @@ -811,15 +806,9 @@ public override async Task Alter_column_make_required() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; -ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL; -ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; +ALTER TABLE `People` ALTER COLUMN `SomeColumn` DROP DEFAULT; +UPDATE `People` SET `SomeColumn` = '' WHERE `SomeColumn` IS NULL; +ALTER TABLE `People` ALTER COLUMN `SomeColumn` varchar(255) NOT NULL DEFAULT ''; """); } @@ -829,15 +818,9 @@ public override async Task Alter_column_make_required_with_null_data() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; -ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL; -ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; +ALTER TABLE `People` ALTER COLUMN `SomeColumn` DROP DEFAULT; +UPDATE `People` SET `SomeColumn` = '' WHERE `SomeColumn` IS NULL; +ALTER TABLE `People` ALTER COLUMN `SomeColumn` varchar(255) NOT NULL DEFAULT ''; """); } @@ -848,17 +831,11 @@ public override async Task Alter_column_make_required_with_index() AssertSql( """ -DROP INDEX [IX_People_SomeColumn] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeColumn'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; -ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(450) NOT NULL; -ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; -CREATE INDEX [IX_People_SomeColumn] ON [People] ([SomeColumn]); +DROP INDEX `IX_People_SomeColumn` ON `People`; +ALTER TABLE `People` ALTER COLUMN `SomeColumn` DROP DEFAULT; +UPDATE `People` SET `SomeColumn` = '' WHERE `SomeColumn` IS NULL; +ALTER TABLE `People` ALTER COLUMN `SomeColumn` varchar(255) NOT NULL DEFAULT ''; +CREATE INDEX `IX_People_SomeColumn` ON `People` (`SomeColumn`); """); } @@ -869,17 +846,11 @@ public override async Task Alter_column_make_required_with_composite_index() AssertSql( """ -DROP INDEX [IX_People_FirstName_LastName] ON [People]; -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -UPDATE [People] SET [FirstName] = N'' WHERE [FirstName] IS NULL; -ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NOT NULL; -ALTER TABLE [People] ADD DEFAULT N'' FOR [FirstName]; -CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]); +DROP INDEX `IX_People_FirstName_LastName` ON `People`; +ALTER TABLE `People` ALTER COLUMN `FirstName` DROP DEFAULT; +UPDATE `People` SET `FirstName` = '' WHERE `FirstName` IS NULL; +ALTER TABLE `People` ALTER COLUMN `FirstName` varchar(255) NOT NULL DEFAULT ''; +CREATE INDEX `IX_People_FirstName_LastName` ON `People` (`FirstName`, `LastName`); """); } @@ -981,10 +952,7 @@ public override async Task Alter_column_add_comment() await base.Alter_column_add_comment(); AssertSql( - """ -ALTER TABLE `People` ALTER COLUMN `Id` DROP DEFAULT; -ALTER TABLE `People` ALTER COLUMN `Id` integer NOT NULL; -"""); + ); } [ConditionalFact] @@ -993,11 +961,7 @@ public override async Task Alter_computed_column_add_comment() await base.Alter_computed_column_add_comment(); AssertSql( - """ -ALTER TABLE `People` ALTER COLUMN `SomeColumn` DROP DEFAULT; -ALTER TABLE `People` DROP COLUMN `SomeColumn`; -ALTER TABLE `People` ADD `SomeColumn` integer NOT NULL DEFAULT 42; -"""); + ); } [ConditionalFact] @@ -1006,10 +970,7 @@ public override async Task Alter_column_change_comment() await base.Alter_column_change_comment(); AssertSql( - """ -ALTER TABLE `People` ALTER COLUMN `Id` DROP DEFAULT; -ALTER TABLE `People` ALTER COLUMN `Id` integer NOT NULL; -"""); + ); } [ConditionalFact] @@ -1018,12 +979,7 @@ public override async Task Alter_column_remove_comment() await base.Alter_column_remove_comment(); AssertSql( - """ -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -DECLARE @description AS sql_variant; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; -"""); + ); } [ConditionalFact] @@ -1099,62 +1055,52 @@ public override async Task Convert_json_entities_to_regular_owned() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedCollection'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Entity] DROP COLUMN [OwnedCollection]; +ALTER TABLE `Entity` ALTER COLUMN `OwnedCollection` DROP DEFAULT; +ALTER TABLE `Entity` DROP COLUMN `OwnedCollection`; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedReference'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var1 + '];'); -ALTER TABLE [Entity] DROP COLUMN [OwnedReference]; +ALTER TABLE `Entity` ALTER COLUMN `OwnedReference` DROP DEFAULT; +ALTER TABLE `Entity` DROP COLUMN `OwnedReference`; """, // """ -ALTER TABLE [Entity] ADD [OwnedReference_Date] datetime2 NULL; +ALTER TABLE `Entity` ADD `OwnedReference_Date` datetime NULL; """, // """ -ALTER TABLE [Entity] ADD [OwnedReference_NestedReference_Number] int NULL; +ALTER TABLE `Entity` ADD `OwnedReference_NestedReference_Number` integer NULL; """, // """ -CREATE TABLE [Entity_NestedCollection] ( - [OwnedEntityId] int NOT NULL, - [Id] int NOT NULL IDENTITY, - [Number2] int NOT NULL, - CONSTRAINT [PK_Entity_NestedCollection] PRIMARY KEY ([OwnedEntityId], [Id]), - CONSTRAINT [FK_Entity_NestedCollection_Entity_OwnedEntityId] FOREIGN KEY ([OwnedEntityId]) REFERENCES [Entity] ([Id]) ON DELETE CASCADE +CREATE TABLE `Entity_NestedCollection` ( + `OwnedEntityId` integer NOT NULL, + `Id` counter NOT NULL, + `Number2` integer NOT NULL, + CONSTRAINT `PK_Entity_NestedCollection` PRIMARY KEY (`OwnedEntityId`, `Id`), + CONSTRAINT `FK_Entity_NestedCollection_Entity_OwnedEntityId` FOREIGN KEY (`OwnedEntityId`) REFERENCES `Entity` (`Id`) ON DELETE CASCADE ); """, // """ -CREATE TABLE [Entity_OwnedCollection] ( - [EntityId] int NOT NULL, - [Id] int NOT NULL IDENTITY, - [Date2] datetime2 NOT NULL, - [NestedReference2_Number3] int NULL, - CONSTRAINT [PK_Entity_OwnedCollection] PRIMARY KEY ([EntityId], [Id]), - CONSTRAINT [FK_Entity_OwnedCollection_Entity_EntityId] FOREIGN KEY ([EntityId]) REFERENCES [Entity] ([Id]) ON DELETE CASCADE +CREATE TABLE `Entity_OwnedCollection` ( + `EntityId` integer NOT NULL, + `Id` counter NOT NULL, + `Date2` datetime NOT NULL, + `NestedReference2_Number3` integer NULL, + CONSTRAINT `PK_Entity_OwnedCollection` PRIMARY KEY (`EntityId`, `Id`), + CONSTRAINT `FK_Entity_OwnedCollection_Entity_EntityId` FOREIGN KEY (`EntityId`) REFERENCES `Entity` (`Id`) ON DELETE CASCADE ); """, // """ -CREATE TABLE [Entity_OwnedCollection_NestedCollection2] ( - [Owned2EntityId] int NOT NULL, - [Owned2Id] int NOT NULL, - [Id] int NOT NULL IDENTITY, - [Number4] int NOT NULL, - CONSTRAINT [PK_Entity_OwnedCollection_NestedCollection2] PRIMARY KEY ([Owned2EntityId], [Owned2Id], [Id]), - CONSTRAINT [FK_Entity_OwnedCollection_NestedCollection2_Entity_OwnedCollection_Owned2EntityId_Owned2Id] FOREIGN KEY ([Owned2EntityId], [Owned2Id]) REFERENCES [Entity_OwnedCollection] ([EntityId], [Id]) ON DELETE CASCADE +CREATE TABLE `Entity_OwnedCollection_NestedCollection2` ( + `Owned2EntityId` integer NOT NULL, + `Owned2Id` integer NOT NULL, + `Id` counter NOT NULL, + `Number4` integer NOT NULL, + CONSTRAINT `PK_Entity_OwnedCollection_NestedCollection2` PRIMARY KEY (`Owned2EntityId`, `Owned2Id`, `Id`), + CONSTRAINT `FK_Entity_OwnedCollection_NestedCollection2_Entity_OwnedCollect~` FOREIGN KEY (`Owned2EntityId`, `Owned2Id`) REFERENCES `Entity_OwnedCollection` (`EntityId`, `Id`) ON DELETE CASCADE ); """); } @@ -1165,43 +1111,33 @@ public override async Task Convert_regular_owned_entities_to_json() AssertSql( """ -DROP TABLE [Entity_NestedCollection]; +DROP TABLE `Entity_NestedCollection`; """, // """ -DROP TABLE [Entity_OwnedCollection_NestedCollection2]; +DROP TABLE `Entity_OwnedCollection_NestedCollection2`; """, // """ -DROP TABLE [Entity_OwnedCollection]; +DROP TABLE `Entity_OwnedCollection`; """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedReference_Date'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Entity] DROP COLUMN [OwnedReference_Date]; +ALTER TABLE `Entity` ALTER COLUMN `OwnedReference_Date` DROP DEFAULT; +ALTER TABLE `Entity` DROP COLUMN `OwnedReference_Date`; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedReference_NestedReference_Number'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var1 + '];'); -ALTER TABLE [Entity] DROP COLUMN [OwnedReference_NestedReference_Number]; +ALTER TABLE `Entity` ALTER COLUMN `OwnedReference_NestedReference_Number` DROP DEFAULT; +ALTER TABLE `Entity` DROP COLUMN `OwnedReference_NestedReference_Number`; """, // """ -ALTER TABLE [Entity] ADD [OwnedCollection] nvarchar(max) NULL; +ALTER TABLE `Entity` ADD `OwnedCollection` longchar NULL; """, // """ -ALTER TABLE [Entity] ADD [OwnedReference] nvarchar(max) NULL; +ALTER TABLE `Entity` ADD `OwnedReference` longchar NULL; """); } @@ -1218,15 +1154,9 @@ public override async Task Convert_string_column_to_a_json_column_containing_req AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); -UPDATE [Entity] SET [Name] = N'{}' WHERE [Name] IS NULL; -ALTER TABLE [Entity] ALTER COLUMN [Name] nvarchar(max) NOT NULL; -ALTER TABLE [Entity] ADD DEFAULT N'{}' FOR [Name]; +ALTER TABLE `Entity` ALTER COLUMN `Name` DROP DEFAULT; +UPDATE `Entity` SET `Name` = '{}' WHERE `Name` IS NULL; +ALTER TABLE `Entity` ALTER COLUMN `Name` longchar NOT NULL DEFAULT '{}'; """); } @@ -1355,18 +1285,13 @@ await Test( model => { var nameColumn = Assert.Single(Assert.Single(model.Tables).Columns); - Assert.Equal("(N'Doe')", nameColumn.DefaultValueSql); + Assert.Equal("Doe", nameColumn.DefaultValue); }); AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] ADD DEFAULT N'Doe' FOR [Name]; +ALTER TABLE `People` ALTER COLUMN `Name` DROP DEFAULT; +ALTER TABLE `People` ALTER COLUMN `Name` varchar(255) NULL DEFAULT 'Doe'; """); } @@ -1381,7 +1306,7 @@ await Test( model => { var nameColumn = Assert.Single(Assert.Single(model.Tables).Columns); - Assert.Equal("(N'Doe')", nameColumn.DefaultValueSql); + Assert.Equal("'Doe'", nameColumn.DefaultValueSql); Assert.Equal("Some comment", nameColumn.Comment); }); @@ -1412,17 +1337,12 @@ public override async Task Drop_column_primary_key() AssertSql( """ -ALTER TABLE [People] DROP CONSTRAINT [PK_People]; +ALTER TABLE `People` DROP CONSTRAINT `PK_People`; """, // """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] DROP COLUMN [Id]; +ALTER TABLE `People` ALTER COLUMN `Id` DROP DEFAULT; +ALTER TABLE `People` DROP COLUMN `Id`; """); } @@ -1458,23 +1378,13 @@ public override async Task Drop_json_columns_from_existing_table() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedCollection'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Entity] DROP COLUMN [OwnedCollection]; +ALTER TABLE `Entity` ALTER COLUMN `OwnedCollection` DROP DEFAULT; +ALTER TABLE `Entity` DROP COLUMN `OwnedCollection`; """, // """ -DECLARE @var1 sysname; -SELECT @var1 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Entity]') AND [c].[name] = N'OwnedReference'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var1 + '];'); -ALTER TABLE [Entity] DROP COLUMN [OwnedReference]; +ALTER TABLE `Entity` ALTER COLUMN `OwnedReference` DROP DEFAULT; +ALTER TABLE `Entity` DROP COLUMN `OwnedReference`; """); } @@ -1508,17 +1418,7 @@ public override async Task Create_index() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL; -""", - // - """ -CREATE INDEX [IX_People_FirstName] ON [People] ([FirstName]); +CREATE INDEX `IX_People_FirstName` ON `People` (`FirstName`); """); } @@ -1558,7 +1458,7 @@ public override async Task Create_index_descending() AssertSql( """ -CREATE INDEX [IX_People_X] ON [People] ([X] DESC); +CREATE INDEX `IX_People_X` ON `People` (`X` DESC); """); } @@ -1568,7 +1468,7 @@ public override async Task Create_index_descending_mixed() AssertSql( """ -CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); +CREATE INDEX `IX_People_X_Y_Z` ON `People` (`X`, `Y` DESC, `Z`); """); } @@ -1592,11 +1492,11 @@ public override async Task Alter_index_change_sort_order() AssertSql( """ -DROP INDEX [IX_People_X_Y_Z] ON [People]; +DROP INDEX `IX_People_X_Y_Z` ON `People`; """, // """ -CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); +CREATE INDEX `IX_People_X_Y_Z` ON `People` (`X`, `Y` DESC, `Z`); """); } @@ -1720,19 +1620,13 @@ public override async Task Add_primary_key_with_name() AssertSql( """ -DECLARE @var0 sysname; -SELECT @var0 = [d].[name] -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeField'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -UPDATE [People] SET [SomeField] = N'' WHERE [SomeField] IS NULL; -ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL; -ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeField]; +ALTER TABLE `People` ALTER COLUMN `SomeField` DROP DEFAULT; +UPDATE `People` SET `SomeField` = '' WHERE `SomeField` IS NULL; +ALTER TABLE `People` ALTER COLUMN `SomeField` varchar(255) NOT NULL DEFAULT ''; """, // """ -ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField]); +ALTER TABLE `People` ADD CONSTRAINT `PK_Foo` PRIMARY KEY (`SomeField`); """); } @@ -2090,16 +1984,16 @@ public override async Task InsertDataOperation() AssertSql( """ -IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Name') AND [object_id] = OBJECT_ID(N'[Person]')) - SET IDENTITY_INSERT [Person] ON; -INSERT INTO [Person] ([Id], [Name]) -VALUES (1, N'Daenerys Targaryen'), -(2, N'John Snow'), -(3, N'Arya Stark'), -(4, N'Harry Strickland'), -(5, NULL); -IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Name') AND [object_id] = OBJECT_ID(N'[Person]')) - SET IDENTITY_INSERT [Person] OFF; +INSERT INTO `Person` (`Id`, `Name`) +VALUES (1, 'Daenerys Targaryen'); +INSERT INTO `Person` (`Id`, `Name`) +VALUES (2, 'John Snow'); +INSERT INTO `Person` (`Id`, `Name`) +VALUES (3, 'Arya Stark'); +INSERT INTO `Person` (`Id`, `Name`) +VALUES (4, 'Harry Strickland'); +INSERT INTO `Person` (`Id`, `Name`) +VALUES (5, NULL); """); }