diff --git a/model/Models/ConstraintColumn.cs b/model/Models/ConstraintColumn.cs index a6118a5a..5c9a4379 100644 --- a/model/Models/ConstraintColumn.cs +++ b/model/Models/ConstraintColumn.cs @@ -11,7 +11,7 @@ public ConstraintColumn(string columnName, bool desc) { } public string Script() { - return "[" + ColumnName + "]" + (Desc ? " DESC" : " ASC"); + return "[" + ColumnName + "]" + (Desc ? " DESC" : ""); } } } diff --git a/model/Models/Database.cs b/model/Models/Database.cs index 5ca4ca92..bb4376f5 100644 --- a/model/Models/Database.cs +++ b/model/Models/Database.cs @@ -539,7 +539,7 @@ from INFORMATION_SCHEMA.TABLE_CONSTRAINTS DELETE_RULE, fk.is_disabled from INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc - inner join sys.foreign_keys fk on rc.CONSTRAINT_NAME = fk.name"; + inner join sys.foreign_keys fk on rc.CONSTRAINT_NAME = fk.name and rc.CONSTRAINT_SCHEMA = OBJECT_SCHEMA_NAME(fk.parent_object_id)"; using (var dr = cm.ExecuteReader()) { while (dr.Read()) { var fk = FindForeignKey((string) dr["CONSTRAINT_NAME"], (string)dr["TABLE_SCHEMA"]); @@ -1227,8 +1227,8 @@ public void ScriptToDir(string tableHint = null, Action log WriteSchemaScript(log); WriteScriptDir("tables", Tables.ToArray(), log); WriteScriptDir("table_types", TableTypes.ToArray(), log); - WriteScriptDir("user_defined_types", UserDefinedTypes.ToArray(), log); - WriteScriptDir("foreign_keys", ForeignKeys.ToArray(), log); + WriteScriptDir("user_defined_types", UserDefinedTypes.ToArray(), log); + WriteScriptDir("foreign_keys", ForeignKeys.OrderBy(x => x.Name).ToArray(), log); foreach (var routineType in Routines.GroupBy(x => x.RoutineType)) { var dir = routineType.Key.ToString().ToLower() + "s"; WriteScriptDir(dir, routineType.ToArray(), log); diff --git a/model/Models/Table.cs b/model/Models/Table.cs index 2dfe3c53..313861a1 100644 --- a/model/Models/Table.cs +++ b/model/Models/Table.cs @@ -138,7 +138,7 @@ public string ScriptCreate() { IsType ? "AS TABLE " : string.Empty); text.Append(Columns.Script()); if (_Constraints.Count > 0) text.AppendLine(); - foreach (var c in _Constraints.Where(c => c.Type != "INDEX")) { + foreach (var c in _Constraints.OrderBy(x => x.Name).Where(c => c.Type != "INDEX")) { text.AppendLine(" ," + c.ScriptCreate()); } text.AppendLine(")"); diff --git a/test/DatabaseTester.cs b/test/DatabaseTester.cs index 5df2d2c8..c2f1743d 100644 --- a/test/DatabaseTester.cs +++ b/test/DatabaseTester.cs @@ -122,31 +122,29 @@ public void TestTableIndexesWithFilter() { var result = db.ScriptCreate(); TestHelper.DropDb("TEST"); - Assert.That(result, Is.StringContaining("CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable] ([Id] ASC) WHERE ([EndDate] IS NULL)")); + Assert.That(result, Is.StringContaining("CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable] ([Id]) WHERE ([EndDate] IS NULL)")); } - [Test] - public void TestViewIndexes() - { - TestHelper.DropDb("TEST"); - TestHelper.ExecSql("CREATE DATABASE TEST", ""); + [Test] + public void TestViewIndexes() { + TestHelper.DropDb("TEST"); + TestHelper.ExecSql("CREATE DATABASE TEST", ""); - TestHelper.ExecSql(@"CREATE TABLE MyTable (Id int, Name nvarchar(250), EndDate datetime)", "TEST"); - TestHelper.ExecSql(@"CREATE VIEW dbo.MyView WITH SCHEMABINDING as SELECT t.Id, t.Name, t.EndDate from dbo.MyTable t", "TEST"); - TestHelper.ExecSql(@"CREATE UNIQUE CLUSTERED INDEX MyIndex ON MyView (Id, Name)", "TEST"); + TestHelper.ExecSql(@"CREATE TABLE MyTable (Id int, Name nvarchar(250), EndDate datetime)", "TEST"); + TestHelper.ExecSql(@"CREATE VIEW dbo.MyView WITH SCHEMABINDING as SELECT t.Id, t.Name, t.EndDate from dbo.MyTable t", "TEST"); + TestHelper.ExecSql(@"CREATE UNIQUE CLUSTERED INDEX MyIndex ON MyView (Id, Name)", "TEST"); - var db = new Database("TEST") - { - Connection = TestHelper.GetConnString("TEST") - }; - db.Load(); - var result = db.ScriptCreate(); - TestHelper.DropDb("TEST"); + var db = new Database("TEST") { + Connection = TestHelper.GetConnString("TEST") + }; + db.Load(); + var result = db.ScriptCreate(); + TestHelper.DropDb("TEST"); - Assert.That(result, Is.StringContaining("CREATE UNIQUE CLUSTERED INDEX [MyIndex] ON [dbo].[MyView] ([Id] ASC, [Name] ASC)")); - } + Assert.That(result, Is.StringContaining("CREATE UNIQUE CLUSTERED INDEX [MyIndex] ON [dbo].[MyView] ([Id], [Name])")); + } - [Test] + [Test] [Ignore("test won't work without license key for sqldbdiff")] public void TestDiffScript() { TestHelper.DropDb("TEST_SOURCE"); @@ -282,7 +280,7 @@ CREATE TYPE [dbo].[MyTableType] AS TABLE( [Value] [varchar](50) NOT NULL, PRIMARY KEY CLUSTERED ( - [ID] ASC + [ID] ) ) @@ -326,7 +324,7 @@ CONSTRAINT [PK_1a] PRIMARY KEY (a) CREATE TABLE [dbo].[t1b] ( a INT NOT NULL, - CONSTRAINT [FKName] FOREIGN KEY ([a]) REFERENCES [dbo].[t1a] ([a]) + CONSTRAINT [FKName] FOREIGN KEY ([a]) REFERENCES [dbo].[t1a] ([a]) ON UPDATE CASCADE ) CREATE TABLE [s2].[t2a] @@ -338,7 +336,7 @@ CONSTRAINT [PK_2a] PRIMARY KEY (a) CREATE TABLE [s2].[t2b] ( a INT NOT NULL, - CONSTRAINT [FKName] FOREIGN KEY ([a]) REFERENCES [s2].[t2a] ([a]) + CONSTRAINT [FKName] FOREIGN KEY ([a]) REFERENCES [s2].[t2a] ([a]) ON DELETE CASCADE ) "; @@ -361,6 +359,11 @@ CONSTRAINT [FKName] FOREIGN KEY ([a]) REFERENCES [s2].[t2a] ([a]) Assert.AreEqual(db.ForeignKeys[0].Name, db.ForeignKeys[1].Name); Assert.AreNotEqual(db.ForeignKeys[0].Table.Owner, db.ForeignKeys[1].Table.Owner); + Assert.AreEqual("CASCADE", db.FindForeignKey("FKName", "dbo").OnUpdate); + Assert.AreEqual("NO ACTION", db.FindForeignKey("FKName", "s2").OnUpdate); + + Assert.AreEqual("NO ACTION", db.FindForeignKey("FKName", "dbo").OnDelete); + Assert.AreEqual("CASCADE", db.FindForeignKey("FKName", "s2").OnDelete); } public void TestScriptViewInsteadOfTrigger() { @@ -440,6 +443,7 @@ public void TestScriptToDir() { loc.Columns.Add(new Column("id", "int", false, null) {Position = 1}); loc.Columns.Add(new Column("policyId", "int", false, null) {Position = 2}); loc.Columns.Add(new Column("storage", "bit", false, null) {Position = 3}); + loc.Columns.Add(new Column("category", "int", false, null) { Position = 4 }); loc.AddConstraint(new Constraint("PK_Location", "PRIMARY KEY", "id") { Clustered = true, Unique = true }); loc.Columns.Items[0].Identity = new Identity(1, 1); @@ -447,10 +451,16 @@ public void TestScriptToDir() { formType.Columns.Add(new Column("code", "tinyint", false, null) {Position = 1}); formType.Columns.Add(new Column("desc", "varchar", 10, false, null) {Position = 2}); formType.AddConstraint(new Constraint("PK_FormType", "PRIMARY KEY", "code") { Clustered = true, Unique = true }); - - var emptyTable = new Table("dbo", "EmptyTable"); - emptyTable.Columns.Add(new Column("code", "tinyint", false, null) {Position = 1}); - emptyTable.AddConstraint(new Constraint("PK_EmptyTable", "PRIMARY KEY", "code") {Clustered = true, Unique = true}); + formType.AddConstraint(Constraint.CreateCheckedConstraint("CK_FormType", false, "([code]<(5))")); + + var categoryType = new Table("dbo", "CategoryType"); + categoryType.Columns.Add(new Column("id", "int", false, null) { Position = 1 }); + categoryType.Columns.Add(new Column("Category", "varchar", 10, false, null) { Position = 2 }); + categoryType.AddConstraint(new Constraint("PK_CategoryType", "PRIMARY KEY", "id") { Clustered = true, Unique = true }); + + var emptyTable = new Table("dbo", "EmptyTable"); + emptyTable.Columns.Add(new Column("code", "tinyint", false, null) {Position = 1}); + emptyTable.AddConstraint(new Constraint("PK_EmptyTable", "PRIMARY KEY", "code") {Clustered = true, Unique = true}); var fk_policy_formType = new ForeignKey("FK_Policy_FormType"); fk_policy_formType.Table = policy; @@ -468,6 +478,14 @@ public void TestScriptToDir() { fk_location_policy.OnUpdate = "NO ACTION"; fk_location_policy.OnDelete = "CASCADE"; + var fk_location_category = new ForeignKey("FK_Location_category"); + fk_location_category.Table = loc; + fk_location_category.Columns.Add("category"); + fk_location_category.RefTable = categoryType; + fk_location_category.RefColumns.Add("id"); + fk_location_category.OnUpdate = "NO ACTION"; + fk_location_category.OnDelete = "CASCADE"; + var tt_codedesc = new Table("dbo", "CodeDesc"); tt_codedesc.IsType = true; tt_codedesc.Columns.Add(new Column("code", "tinyint", false, null) { Position = 1 }); @@ -477,11 +495,13 @@ public void TestScriptToDir() { var db = new Database("ScriptToDirTest"); db.Tables.Add(policy); db.Tables.Add(formType); - db.Tables.Add(emptyTable); + db.Tables.Add(categoryType); + db.Tables.Add(emptyTable); db.Tables.Add(loc); db.TableTypes.Add(tt_codedesc); db.ForeignKeys.Add(fk_policy_formType); db.ForeignKeys.Add(fk_location_policy); + db.ForeignKeys.Add(fk_location_category); db.FindProp("COMPATIBILITY_LEVEL").Value = "110"; db.FindProp("COLLATE").Value = "SQL_Latin1_General_CP1_CI_AS"; db.FindProp("AUTO_CLOSE").Value = "OFF"; @@ -538,8 +558,23 @@ public void TestScriptToDir() { } } foreach (var t in db.Tables) { - Assert.IsTrue(File.Exists(db.Name + "\\tables\\" + t.Name + ".sql")); - } + var tblFile = db.Name + "\\tables\\" + t.Name + ".sql"; + Assert.IsTrue(File.Exists(tblFile)); + + // Test that the constraints are ordered in the file + string script = File.ReadAllText(tblFile); + int cindex = -1; + + foreach (var ckobject in t.Constraints.OrderBy(x => x.Name)) + { + var thisindex = script.IndexOf(ckobject.ScriptCreate()); + Assert.Greater(thisindex, cindex, "Constraints are not ordered."); + + cindex = thisindex; + } + + + } foreach (var t in db.TableTypes) { Assert.IsTrue(File.Exists(db.Name + "\\table_types\\TYPE_" + t.Name + ".sql")); } @@ -547,6 +582,28 @@ public void TestScriptToDir() { Assert.IsTrue(File.Exists(expected), "File does not exist" + expected); } + + // Test that the foreign keys are ordered in the file + foreach (var t in db.Tables) + { + var fksFile = db.Name + "\\foreign_keys\\" + t.Name + ".sql"; + + if (File.Exists(fksFile)) + { + string script = File.ReadAllText(fksFile); + int fkindex = -1; + + foreach (var fkobject in db.ForeignKeys.Where(x => x.Table == t).OrderBy(x => x.Name)) + { + var thisindex = script.IndexOf(fkobject.ScriptCreate()); + Assert.Greater(thisindex, fkindex, "Foreign keys are not ordered."); + + fkindex = thisindex; + } + } + + } + var copy = new Database("ScriptToDirTestCopy"); copy.Dir = db.Dir; copy.Connection = ConfigHelper.TestDB.Replace("database=TESTDB", "database=" + copy.Name);