From 91f1e90dadcffc39003069629cba82b770981168 Mon Sep 17 00:00:00 2001 From: Seth Reno Date: Wed, 8 Feb 2017 08:12:55 -0600 Subject: [PATCH] Fix columnstore indexes not working https://github.com/sethreno/schemazen/issues/117 --- model/Models/Constraint.cs | 12 +++------ model/Models/Database.cs | 8 +++--- model/Schemazen.Library.csproj | 1 + model/StringUtil.cs | 29 ++++++++++++++++++++ test/ConstraintTester.cs | 19 +++++++++++--- test/DatabaseTester.cs | 48 +++++++++++++++++++++++++--------- test/TableTester.cs | 11 ++++++-- 7 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 model/StringUtil.cs diff --git a/model/Models/Constraint.cs b/model/Models/Constraint.cs index 0922dfcd..b4639cce 100644 --- a/model/Models/Constraint.cs +++ b/model/Models/Constraint.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Linq; +using SchemaZen.Library.Extensions; namespace SchemaZen.Library.Models { public class Constraint : INameable, IScriptable { - public bool Clustered { get; set; } - public bool ColumnStore { get; set; } + public string IndexType { get; set; } public List Columns { get; set; } = new List(); public List IncludedColumns { get; set; } = new List(); public string Name { get; set; } @@ -31,10 +31,6 @@ public static Constraint CreateCheckedConstraint(string name, bool isNotForRepli return constraint; } - public string ColumnStoreText => !ColumnStore ? "" : " COLUMNSTORE"; - - public string ClusteredText => !Clustered ? "NONCLUSTERED" : "CLUSTERED"; - public string UniqueText => Type != " PRIMARY KEY" && !Unique ? "" : " UNIQUE"; public string ScriptCreate() { @@ -43,7 +39,7 @@ public string ScriptCreate() { var notForReplicationOption = _isNotForReplication ? "NOT FOR REPLICATION" : ""; return $"CONSTRAINT [{Name}] CHECK {notForReplicationOption} {_checkConstraintExpression}"; case "INDEX": - var sql = $"CREATE{UniqueText} {ClusteredText}{ColumnStoreText} INDEX [{Name}] ON [{Table.Owner}].[{Table.Name}] ({string.Join(", ", Columns.Select(c => c.Script()).ToArray())})"; + var sql = $"CREATE{UniqueText}{IndexType.Space()} INDEX [{Name}] ON [{Table.Owner}].[{Table.Name}] ({string.Join(", ", Columns.Select(c => c.Script()).ToArray())})"; if (IncludedColumns.Count > 0) { sql += $" INCLUDE ([{string.Join("], [", IncludedColumns.ToArray())}])"; } @@ -53,7 +49,7 @@ public string ScriptCreate() { return sql; } - return (Table.IsType ? string.Empty : $"CONSTRAINT [{Name}] ") + $"{Type} {ClusteredText} ({string.Join(", ", Columns.Select(c => c.Script()).ToArray())})"; + return (Table.IsType ? string.Empty : $"CONSTRAINT [{Name}] ") + $"{Type}{IndexType.Space()} ({string.Join(", ", Columns.Select(c => c.Script()).ToArray())})"; } } } diff --git a/model/Models/Database.cs b/model/Models/Database.cs index 9727e709..450ec41f 100644 --- a/model/Models/Database.cs +++ b/model/Models/Database.cs @@ -590,7 +590,8 @@ private void LoadConstraintsAndIndexes(SqlCommand cm) { i.type_desc, i.filter_definition, isnull(ic.is_included_column, 0) as is_included_column, - ic.is_descending_key + ic.is_descending_key, + i.type from ( select object_id, name, schema_id, 'T' as baseType from sys.tables @@ -633,10 +634,9 @@ from sys.table_types ViewIndexes.Add(c); } } - c.Clustered = (string)dr["type_desc"] == "CLUSTERED"; + c.IndexType = dr["type_desc"] as string; c.Unique = (bool)dr["is_unique"]; - var filter = dr["filter_definition"].ToString(); //can be null - c.Filter = filter; + c.Filter = dr["filter_definition"] as string; if ((bool)dr["is_included_column"]) { c.IncludedColumns.Add((string)dr["columnName"]); } else { diff --git a/model/Schemazen.Library.csproj b/model/Schemazen.Library.csproj index aeba829d..f68eb667 100644 --- a/model/Schemazen.Library.csproj +++ b/model/Schemazen.Library.csproj @@ -80,6 +80,7 @@ + diff --git a/model/StringUtil.cs b/model/StringUtil.cs new file mode 100644 index 00000000..5ca5b643 --- /dev/null +++ b/model/StringUtil.cs @@ -0,0 +1,29 @@ +using System; + +namespace SchemaZen.Library { + + public class StringUtil { + + /// + /// Adds a space to the beginning of a string. + /// If the string is null or empty it's returned as-is + /// + public static string AddSpaceIfNotEmpty(string val) { + if (String.IsNullOrEmpty(val)) return val; + return $" {val}"; + } + } + + namespace Extensions { + + /// + /// Extension methods to make sql script generators more readable. + /// + public static class Strings { + + public static string Space(this String val) { + return StringUtil.AddSpaceIfNotEmpty(val); + } + } + } +} diff --git a/test/ConstraintTester.cs b/test/ConstraintTester.cs index f29d48da..694ba63a 100644 --- a/test/ConstraintTester.cs +++ b/test/ConstraintTester.cs @@ -18,7 +18,7 @@ private static Constraint SetUp() { [Test] public void clustered_index() { var c = SetUp(); - c.Clustered = true; + c.IndexType = "CLUSTERED"; Assert.AreEqual( "CREATE CLUSTERED INDEX [test] ON [dbo].[test] ([a], [b])", c.ScriptCreate()); @@ -27,16 +27,25 @@ public void clustered_index() { [Test] public void nonclustered_index() { var c = SetUp(); - c.Clustered = false; + c.IndexType = "NONCLUSTERED"; Assert.AreEqual( "CREATE NONCLUSTERED INDEX [test] ON [dbo].[test] ([a], [b])", c.ScriptCreate()); } [Test] - public void columnstore_index() { + public void clustered_columnstore_index() { var c = SetUp(); - c.ColumnStore = true; + c.IndexType = "CLUSTERED COLUMNSTORE"; + Assert.AreEqual( + "CREATE CLUSTERED COLUMNSTORE INDEX [test] ON [dbo].[test] ([a], [b])", + c.ScriptCreate()); + } + + [Test] + public void nonclustered_columnstore_index() { + var c = SetUp(); + c.IndexType = "NONCLUSTERED COLUMNSTORE"; Assert.AreEqual( "CREATE NONCLUSTERED COLUMNSTORE INDEX [test] ON [dbo].[test] ([a], [b])", c.ScriptCreate()); @@ -46,6 +55,7 @@ public void columnstore_index() { public void primary_key() { var c = SetUp(); c.Type = "PRIMARY KEY"; + c.IndexType = "NONCLUSTERED"; Assert.AreEqual( "CONSTRAINT [test] PRIMARY KEY NONCLUSTERED ([a], [b])", c.ScriptCreate()); @@ -55,6 +65,7 @@ public void primary_key() { public void foreign_key() { var c = SetUp(); c.Type = "FOREIGN KEY"; + c.IndexType = "NONCLUSTERED"; Assert.AreEqual( "CONSTRAINT [test] FOREIGN KEY NONCLUSTERED ([a], [b])", c.ScriptCreate()); diff --git a/test/DatabaseTester.cs b/test/DatabaseTester.cs index 625a252c..5e8d375d 100644 --- a/test/DatabaseTester.cs +++ b/test/DatabaseTester.cs @@ -209,16 +209,20 @@ public void TestScript() { var t1 = new Table("dbo", "t1"); t1.Columns.Add(new Column("col1", "int", false, null) { Position = 1 }); t1.Columns.Add(new Column("col2", "int", false, null) { Position = 2 }); - t1.AddConstraint(new Constraint("pk_t1", "PRIMARY KEY", "col1,col2")); - t1.FindConstraint("pk_t1").Clustered = true; + t1.AddConstraint(new Constraint("pk_t1", "PRIMARY KEY", "col1,col2") { + IndexType = "CLUSTERED" + }); var t2 = new Table("dbo", "t2"); t2.Columns.Add(new Column("col1", "int", false, null) { Position = 1 }); t2.Columns.Add(new Column("col2", "int", false, null) { Position = 2 }); t2.Columns.Add(new Column("col3", "int", false, null) { Position = 3 }); - t2.AddConstraint(new Constraint("pk_t2", "PRIMARY KEY", "col1")); - t2.FindConstraint("pk_t2").Clustered = true; - t2.AddConstraint(new Constraint("IX_col3", "UNIQUE", "col3")); + t2.AddConstraint(new Constraint("pk_t2", "PRIMARY KEY", "col1") { + IndexType = "CLUSTERED" + }); + t2.AddConstraint(new Constraint("IX_col3", "UNIQUE", "col3") { + IndexType = "NONCLUSTERED" + }); db.ForeignKeys.Add(new ForeignKey(t2, "fk_t2_t1", "col2,col3", t1, "col1,col2")); @@ -236,8 +240,9 @@ public void TestScript() { TestHelper.DropDb("TEST_TEMP"); foreach (var t in db.Tables) { - Assert.IsNotNull(db2.FindTable(t.Name, t.Owner)); - Assert.IsFalse(db2.FindTable(t.Name, t.Owner).Compare(t).IsDiff); + var copy = db2.FindTable(t.Name, t.Owner); + Assert.IsNotNull(copy); + Assert.IsFalse(copy.Compare(t).IsDiff); } } @@ -489,7 +494,10 @@ public void TestScriptToDir() { var policy = new Table("dbo", "Policy"); policy.Columns.Add(new Column("id", "int", false, null) { Position = 1 }); policy.Columns.Add(new Column("form", "tinyint", false, null) { Position = 2 }); - policy.AddConstraint(new Constraint("PK_Policy", "PRIMARY KEY", "id") { Clustered = true, Unique = true }); + policy.AddConstraint(new Constraint("PK_Policy", "PRIMARY KEY", "id") { + IndexType = "CLUSTERED", + Unique = true + }); policy.Columns.Items[0].Identity = new Identity(1, 1); var loc = new Table("dbo", "Location"); @@ -497,23 +505,35 @@ public void TestScriptToDir() { 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.AddConstraint(new Constraint("PK_Location", "PRIMARY KEY", "id") { + IndexType = "CLUSTERED", + Unique = true + }); loc.Columns.Items[0].Identity = new Identity(1, 1); var formType = new Table("dbo", "FormType"); 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 }); + formType.AddConstraint(new Constraint("PK_FormType", "PRIMARY KEY", "code") { + IndexType = "CLUSTERED", + 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 }); + categoryType.AddConstraint(new Constraint("PK_CategoryType", "PRIMARY KEY", "id") { + IndexType = "CLUSTERED", + 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 }); + emptyTable.AddConstraint(new Constraint("PK_EmptyTable", "PRIMARY KEY", "code") { + IndexType = "CLUSTERED", + Unique = true + }); var fk_policy_formType = new ForeignKey("FK_Policy_FormType"); fk_policy_formType.Table = policy; @@ -543,7 +563,9 @@ public void TestScriptToDir() { tt_codedesc.IsType = true; tt_codedesc.Columns.Add(new Column("code", "tinyint", false, null) { Position = 1 }); tt_codedesc.Columns.Add(new Column("desc", "varchar", 10, false, null) { Position = 2 }); - tt_codedesc.AddConstraint(new Constraint("PK_CodeDesc", "PRIMARY KEY", "code")); + tt_codedesc.AddConstraint(new Constraint("PK_CodeDesc", "PRIMARY KEY", "code") { + IndexType = "NONCLUSTERED" + }); var db = new Database("ScriptToDirTest"); db.Tables.Add(policy); diff --git a/test/TableTester.cs b/test/TableTester.cs index 4e877661..556e9829 100644 --- a/test/TableTester.cs +++ b/test/TableTester.cs @@ -208,8 +208,15 @@ 3 F Frozen public void TestLargeAmountOfRowsImportAndExport() { var t = new Table("dbo", "TestData"); t.Columns.Add(new Column("test_field", "int", false, null)); - t.AddConstraint(new Constraint("PK_TestData", "PRIMARY KEY", "test_field")); - t.AddConstraint(new Constraint("IX_TestData_PK", "INDEX", "test_field") { Clustered = true, Table = t, Unique = true }); // clustered index is required to ensure the row order is the same as what we import + t.AddConstraint(new Constraint("PK_TestData", "PRIMARY KEY", "test_field") { + IndexType = "NONCLUSTERED" + }); + t.AddConstraint(new Constraint("IX_TestData_PK", "INDEX", "test_field") { + // clustered index is required to ensure the row order is the same as what we import + IndexType = "CLUSTERED", + Table = t, + Unique = true + }); var conn = TestHelper.GetConnString("TESTDB"); DBHelper.DropDb(conn);