/*
 *    The contents of this file are subject to the Initial
 *    Developer's Public License Version 1.0 (the "License");
 *    you may not use this file except in compliance with the
 *    License. You may obtain a copy of the License at
 *    https://github.com/FirebirdSQL/NETProvider/raw/master/license.txt.
 *
 *    Software distributed under the License is distributed on
 *    an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
 *    express or implied. See the License for the specific
 *    language governing rights and limitations under the License.
 *
 *    All Rights Reserved.
 */

//$Authors = Jiri Cincura (jiri@cincura.net)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FirebirdSql.EntityFrameworkCore.Firebird.Metadata;
using FirebirdSql.EntityFrameworkCore.Firebird.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using NUnit.Framework;

namespace FirebirdSql.EntityFrameworkCore.Firebird.Tests.Migrations;
#pragma warning disable EF1001
public class MigrationsTests : EntityFrameworkCoreTestsBase
{
	[Test]
	public async Task CreateTable()
	{
		var operation = new CreateTableOperation
		{
			Name = "People",
			Columns =
				{
						new AddColumnOperation
						{
							Name = "Id",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = false,
							[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.None,
						},
						new AddColumnOperation
						{
							Name = "Id_Identity",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = false,
							[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.IdentityColumn,
						},
						new AddColumnOperation
						{
							Name = "Id_Sequence",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = false,
							[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.SequenceTrigger,
						},
						new AddColumnOperation
						{
							Name = "EmployerId",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "SSN",
							Table = "People",
							ClrType = typeof(string),
							ColumnType = "char(11)",
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "DEF_O",
							Table = "People",
							ClrType = typeof(string),
							MaxLength = 20,
							DefaultValue = "test",
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "DEF_S",
							Table = "People",
							ClrType = typeof(string),
							MaxLength = 20,
							DefaultValueSql = "'x'",
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "COLLA",
							Table = "People",
							ClrType = typeof(string),
							MaxLength = 20,
							IsNullable = true,
							Collation = "UNICODE_CI_AI"
						},
				},
			PrimaryKey = new AddPrimaryKeyOperation
			{
				Columns = new[] { "Id" },
			},
			UniqueConstraints =
				{
						new AddUniqueConstraintOperation
						{
							Columns = new[] { "SSN" },
						},
				},
			ForeignKeys =
				{
						new AddForeignKeyOperation
						{
							Columns = new[] { "EmployerId" },
							PrincipalTable = "Companies",
							PrincipalColumns = new[] { "Id" },
						},
				},
		};
		var expectedCreateTable = @"CREATE TABLE ""People"" (
    ""Id"" INTEGER NOT NULL,
    ""Id_Identity"" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    ""Id_Sequence"" INTEGER NOT NULL,
    ""EmployerId"" INTEGER,
    ""SSN"" char(11),
    ""DEF_O"" VARCHAR(20) DEFAULT _UTF8'test',
    ""DEF_S"" VARCHAR(20) DEFAULT 'x',
    ""COLLA"" VARCHAR(20) COLLATE UNICODE_CI_AI,
    PRIMARY KEY (""Id""),
    UNIQUE (""SSN""),
    FOREIGN KEY (""EmployerId"") REFERENCES ""Companies"" (""Id"") ON UPDATE NO ACTION ON DELETE NO ACTION
);";
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(3, batch.Count());
		Assert.AreEqual(NewLineEnd(expectedCreateTable), batch[0].CommandText);
		StringAssert.Contains("rdb$generator_name = ", batch[1].CommandText);
		StringAssert.StartsWith("CREATE TRIGGER ", batch[2].CommandText);
	}

	[Test]
	public async Task CreateTableScript()
	{
		var operation = new CreateTableOperation
		{
			Name = "People",
			Columns =
				{
						new AddColumnOperation
						{
							Name = "Id",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = false,
							[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.None,
						},
						new AddColumnOperation
						{
							Name = "Id_Identity",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = false,
							[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.IdentityColumn,
						},
						new AddColumnOperation
						{
							Name = "Id_Sequence",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = false,
							[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.SequenceTrigger,
						},
						new AddColumnOperation
						{
							Name = "EmployerId",
							Table = "People",
							ClrType = typeof(int),
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "SSN",
							Table = "People",
							ClrType = typeof(string),
							ColumnType = "char(11)",
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "DEF_O",
							Table = "People",
							ClrType = typeof(string),
							MaxLength = 20,
							DefaultValue = "test",
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "DEF_S",
							Table = "People",
							ClrType = typeof(string),
							MaxLength = 20,
							DefaultValueSql = "'x'",
							IsNullable = true,
						},
						new AddColumnOperation
						{
							Name = "COLLA",
							Table = "People",
							ClrType = typeof(string),
							MaxLength = 20,
							IsNullable = true,
							Collation = "UNICODE_CI_AI"
						},
				},
			PrimaryKey = new AddPrimaryKeyOperation
			{
				Columns = new[] { "Id" },
			},
			UniqueConstraints =
				{
						new AddUniqueConstraintOperation
						{
							Columns = new[] { "SSN" },
						},
				},
			ForeignKeys =
				{
						new AddForeignKeyOperation
						{
							Columns = new[] { "EmployerId" },
							PrincipalTable = "Companies",
							PrincipalColumns = new[] { "Id" },
						},
				},
		};
		var expectedCreateTable = @"CREATE TABLE ""People"" (
    ""Id"" INTEGER NOT NULL,
    ""Id_Identity"" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    ""Id_Sequence"" INTEGER NOT NULL,
    ""EmployerId"" INTEGER,
    ""SSN"" char(11),
    ""DEF_O"" VARCHAR(20) DEFAULT _UTF8'test',
    ""DEF_S"" VARCHAR(20) DEFAULT 'x',
    ""COLLA"" VARCHAR(20) COLLATE UNICODE_CI_AI,
    PRIMARY KEY (""Id""),
    UNIQUE (""SSN""),
    FOREIGN KEY (""EmployerId"") REFERENCES ""Companies"" (""Id"") ON UPDATE NO ACTION ON DELETE NO ACTION
);";
		var batch = await Generate(new[] { operation }, MigrationsSqlGenerationOptions.Script);
		Assert.AreEqual(5, batch.Count());
		Assert.AreEqual(NewLineEnd(expectedCreateTable), batch[0].CommandText);
		Assert.AreEqual(NewLineEnd("SET TERM ~;"), batch[1].CommandText);
		StringAssert.Contains("rdb$generator_name = ", batch[2].CommandText);
		StringAssert.StartsWith("CREATE TRIGGER ", batch[3].CommandText);
		Assert.AreEqual(NewLineEnd("SET TERM ;~"), batch[4].CommandText);
	}

	[Test]
	public async Task DropTable()
	{
		var operation = new DropTableOperation()
		{
			Name = "People",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"DROP TABLE ""People"";"), batch[0].CommandText);
	}

	[Test]
	public async Task AddColumn()
	{
		var operation = new AddColumnOperation()
		{
			Table = "People",
			Name = "NewColumn",
			ClrType = typeof(decimal),
			Schema = "schema",
			IsNullable = false,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""schema"".""People"" ADD ""NewColumn"" DECIMAL(18,2) NOT NULL;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddColumnWithCollation()
	{
		var operation = new AddColumnOperation()
		{
			Table = "People",
			Name = "NewColumn",
			ClrType = typeof(string),
			MaxLength = 10,
			IsNullable = false,
			Collation = "UNICODE_CI_AI",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD ""NewColumn"" VARCHAR(10) COLLATE UNICODE_CI_AI NOT NULL;"), batch[0].CommandText);
	}

	[Test]
	public async Task DropColumn()
	{
		var operation = new DropColumnOperation()
		{
			Table = "People",
			Name = "DropMe",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" DROP ""DropMe"";"), batch[0].CommandText);
	}

	[Test]
	public async Task AlterColumnLength()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(string),
			IsNullable = true,
			MaxLength = 200,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(string),
				IsNullable = true,
				MaxLength = 100,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(2, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE VARCHAR(200);"), batch[1].CommandText);
	}

	[Test]
	public async Task AlterColumnNullableToNotNull()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(string),
			IsNullable = false,
			MaxLength = 100,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(string),
				IsNullable = true,
				MaxLength = 100,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(3, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" DROP NOT NULL;"), batch[0].CommandText);
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE VARCHAR(100);"), batch[1].CommandText);
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" SET NOT NULL;"), batch[2].CommandText);
	}

	[Test]
	public async Task AlterColumnNotNullToNullable()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(string),
			IsNullable = true,
			MaxLength = 100,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(string),
				IsNullable = false,
				MaxLength = 100,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(2, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" DROP NOT NULL;"), batch[0].CommandText);
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE VARCHAR(100);"), batch[1].CommandText);
	}

	[Test]
	public async Task AlterColumnType()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(long),
			IsNullable = false,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(int),
				IsNullable = false,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(3, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE BIGINT;"), batch[1].CommandText);
	}

	[Test]
	public async Task AlterColumnDefault()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(int),
			DefaultValue = 20,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(int),
				DefaultValue = 10,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(5, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE INTEGER;"), batch[1].CommandText);
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" DROP DEFAULT;"), batch[3].CommandText);
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" SET DEFAULT 20;"), batch[4].CommandText);
	}

	[Test]
	public async Task AlterColumnAddIdentityColumn()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(int),
			[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.IdentityColumn,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(int),
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(3, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE INTEGER GENERATED BY DEFAULT AS IDENTITY;"), batch[1].CommandText);
	}

	[Test]
	public async Task AlterColumnAddSequenceTrigger()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(int),
			[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.SequenceTrigger,
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(int),
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(5, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE INTEGER;"), batch[1].CommandText);
		StringAssert.Contains("rdb$generator_name = ", batch[3].CommandText);
		StringAssert.StartsWith("CREATE TRIGGER ", batch[4].CommandText);
	}

	[Test]
	public async Task AlterColumnRemoveSequenceTrigger()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(int),
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(int),
				[FbAnnotationNames.ValueGenerationStrategy] = FbValueGenerationStrategy.SequenceTrigger,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(4, batch.Count());
		StringAssert.Contains("drop trigger", batch[0].CommandText);
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE INTEGER;"), batch[2].CommandText);
	}

	[Test]
	public async Task AlterColumnCollation()
	{
		var operation = new AlterColumnOperation()
		{
			Table = "People",
			Name = "Col",
			ClrType = typeof(string),
			MaxLength = 10,
			IsNullable = false,
			Collation = "UNICODE_CI_AI",
			OldColumn = new AddColumnOperation()
			{
				ClrType = typeof(string),
				MaxLength = 10,
				IsNullable = false,
			},
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(3, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""Col"" TYPE VARCHAR(10) COLLATE UNICODE_CI_AI;"), batch[1].CommandText);
	}

	[Test]
	public async Task RenameColumn()
	{
		var operation = new RenameColumnOperation()
		{
			Table = "People",
			Name = "OldCol",
			NewName = "NewCol",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ALTER COLUMN ""OldCol"" TO ""NewCol"";"), batch[0].CommandText);
	}

	[Test]
	public async Task CreateIndexOneColumn()
	{
		var operation = new CreateIndexOperation()
		{
			Table = "People",
			Name = "MyIndex",
			Columns = new[] { "Foo" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"CREATE INDEX ""MyIndex"" ON ""People"" (""Foo"");"), batch[0].CommandText);
	}

	[Test]
	public async Task CreateIndexThreeColumn()
	{
		var operation = new CreateIndexOperation()
		{
			Table = "People",
			Name = "MyIndex",
			Columns = new[] { "Foo", "Bar", "Baz" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"CREATE INDEX ""MyIndex"" ON ""People"" (""Foo"", ""Bar"", ""Baz"");"), batch[0].CommandText);
	}

	[Test]
	public async Task CreateIndexUnique()
	{
		var operation = new CreateIndexOperation()
		{
			Table = "People",
			Name = "MyIndex",
			Columns = new[] { "Foo" },
			IsUnique = true,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"CREATE UNIQUE INDEX ""MyIndex"" ON ""People"" (""Foo"");"), batch[0].CommandText);
	}

	[Test]
	public async Task CreateIndexFilter()
	{
		var operation = new CreateIndexOperation()
		{
			Table = "People",
			Name = "MyIndex",
			Filter = "xxx",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"CREATE INDEX ""MyIndex"" ON ""People"" COMPUTED BY (xxx);"), batch[0].CommandText);
	}

	[Test]
	public async Task CreateSequence()
	{
		var operation = new CreateSequenceOperation()
		{
			Name = "MySequence",
			StartValue = 34,
			IncrementBy = 56,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"CREATE SEQUENCE ""MySequence"" START WITH 34 INCREMENT BY 56;"), batch[0].CommandText);
	}

	[Test]
	public async Task AlterSequence()
	{
		var operation = new AlterSequenceOperation()
		{
			Name = "MySequence",
			IncrementBy = 12,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER SEQUENCE ""MySequence"" RESTART INCREMENT BY 12;"), batch[0].CommandText);
	}

	[Test]
	public async Task RestartSequence()
	{
		var operation = new RestartSequenceOperation()
		{
			Name = "MySequence",
			StartValue = 23,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER SEQUENCE ""MySequence"" RESTART WITH 23;"), batch[0].CommandText);
	}

	[Test]
	public async Task RestartSequenceNoValue()
	{
		var operation = new RestartSequenceOperation()
		{
			Name = "MySequence",
			StartValue = null,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER SEQUENCE ""MySequence"" RESTART;"), batch[0].CommandText);
	}

	[Test]
	public async Task DropSequence()
	{
		var operation = new DropSequenceOperation()
		{
			Name = "MySequence",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"DROP SEQUENCE ""MySequence"";"), batch[0].CommandText);
	}

	[Test]
	public async Task AddPrimaryKey()
	{
		var operation = new AddPrimaryKeyOperation()
		{
			Table = "People",
			Name = "PK_People",
			Columns = new[] { "Foo" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_People"" PRIMARY KEY (""Foo"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddPrimaryKeyNoName()
	{
		var operation = new AddPrimaryKeyOperation()
		{
			Table = "People",
			Columns = new[] { "Foo" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD PRIMARY KEY (""Foo"");"), batch[0].CommandText);
	}

	[Test]
	public async Task DropPrimaryKey()
	{
		var operation = new DropPrimaryKeyOperation()
		{
			Table = "People",
			Name = "PK_People",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKey()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyNoName()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyDeleteCascade()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Cascade,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON DELETE CASCADE;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyDeleteNoAction()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.NoAction,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON DELETE NO ACTION;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyDeleteRestrict()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyDeleteSetDefault()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.SetDefault,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON DELETE SET DEFAULT;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyDeleteSetNull()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.SetNull,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON DELETE SET NULL;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyUpdateCascade()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.Cascade,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON UPDATE CASCADE;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyUpdateNoAction()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.NoAction,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON UPDATE NO ACTION;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyUpdateRestrict()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.Restrict,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyUpdateSetDefault()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.SetDefault,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON UPDATE SET DEFAULT;"), batch[0].CommandText);
	}

	[Test]
	public async Task AddForeignKeyUpdateSetNull()
	{
		var operation = new AddForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
			Columns = new[] { "Foo" },
			PrincipalTable = "Principal",
			PrincipalColumns = new[] { "Bar" },
			OnDelete = ReferentialAction.Restrict,
			OnUpdate = ReferentialAction.SetNull,
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""FK_People_Principal"" FOREIGN KEY (""Foo"") REFERENCES ""Principal"" (""Bar"") ON UPDATE SET NULL;"), batch[0].CommandText);
	}

	[Test]
	public async Task DropForeignKey()
	{
		var operation = new DropForeignKeyOperation()
		{
			Table = "People",
			Name = "FK_People_Principal",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" DROP CONSTRAINT ""FK_People_Principal"";"), batch[0].CommandText);
	}

	[Test]
	public async Task AddUniqueConstraintOneColumn()
	{
		var operation = new AddUniqueConstraintOperation()
		{
			Table = "People",
			Name = "UNQ_People_Foo",
			Columns = new[] { "Foo" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""UNQ_People_Foo"" UNIQUE (""Foo"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddUniqueConstraintTwoColumns()
	{
		var operation = new AddUniqueConstraintOperation()
		{
			Table = "People",
			Name = "UNQ_People_Foo_Bar",
			Columns = new[] { "Foo", "Bar" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD CONSTRAINT ""UNQ_People_Foo_Bar"" UNIQUE (""Foo"", ""Bar"");"), batch[0].CommandText);
	}

	[Test]
	public async Task AddUniqueConstraintNoName()
	{
		var operation = new AddUniqueConstraintOperation()
		{
			Table = "People",
			Columns = new[] { "Foo" },
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" ADD UNIQUE (""Foo"");"), batch[0].CommandText);
	}

	[Test]
	public async Task DropUniqueConstraint()
	{
		var operation = new DropUniqueConstraintOperation()
		{
			Table = "People",
			Name = "UNQ_People_Foo",
		};
		var batch = await Generate(new[] { operation });
		Assert.AreEqual(1, batch.Count());
		Assert.AreEqual(NewLineEnd(@"ALTER TABLE ""People"" DROP CONSTRAINT ""UNQ_People_Foo"";"), batch[0].CommandText);
	}

	async Task<IReadOnlyList<MigrationCommand>> Generate(IReadOnlyList<MigrationOperation> operations, MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default)
	{
		await using (var db = await GetDbContext<FbTestDbContext>())
		{
			var generator = db.GetService<IMigrationsSqlGenerator>();
			return generator.Generate(operations, db.Model, options);
		}
	}

	static string NewLineEnd(string s) => s + Environment.NewLine;
}
