using OpenDBDiff.Abstractions.Schema; using OpenDBDiff.Abstractions.Schema.Model; using System; using System.Globalization; using System.Text; namespace OpenDBDiff.SqlServer.Schema.Model { public class Constraint : SQLServerSchemaBase { public enum ConstraintType { None = 0, PrimaryKey = 1, ForeignKey = 2, Default = 3, Unique = 4, Check = 5 } public Constraint(ISchemaBase parent) : this(parent, false) { } public Constraint(ISchemaBase parent, bool hasIndex) : base(parent, ObjectType.Constraint) { this.Columns = new ConstraintColumns(this); if (hasIndex) this.Index = new Index(parent); } /// /// Clona el objeto Column en una nueva instancia. /// public override ISchemaBase Clone(ISchemaBase parent) { Constraint col = new Constraint(parent); col.Id = this.Id; col.Name = this.Name; col.NotForReplication = this.NotForReplication; col.RelationalTableFullName = this.RelationalTableFullName; col.Status = this.Status; col.Type = this.Type; col.WithNoCheck = this.WithNoCheck; col.OnDeleteCascade = this.OnDeleteCascade; col.OnUpdateCascade = this.OnUpdateCascade; col.Owner = this.Owner; col.Columns = this.Columns.Clone(); col.Index = (Index)this.Index?.Clone(parent); col.IsDisabled = this.IsDisabled; col.Definition = this.Definition; col.Guid = this.Guid; return col; } /// /// Informacion sobre le indice asociado al Constraint. /// public Index Index { get; set; } /// /// Coleccion de columnas de la constraint. /// public ConstraintColumns Columns { get; set; } /// /// Indica si la constraint tiene asociada un indice Clustered. /// public Boolean HasClusteredIndex { get { if (Index != null) return (Index.Type == Index.IndexTypeEnum.Clustered); return false; } } /// /// Gets or sets a value indicating whether this constraint is disabled. /// /// /// true if this constraint is disabled; otherwise, false. /// public Boolean IsDisabled { get; set; } /// /// Gets or sets the on delete cascade (only for FK). /// /// The on delete cascade. public int OnDeleteCascade { get; set; } /// /// Gets or sets the on update cascade (only for FK). /// /// The on update cascade. public int OnUpdateCascade { get; set; } /// /// Valor de la constraint (se usa para los Check Constraint). /// public string Definition { get; set; } /// /// Indica si la constraint va a ser usada en replicacion. /// public Boolean NotForReplication { get; set; } /// /// Gets or sets a value indicating whether [with no check]. /// /// true if [with no check]; otherwise, false. public Boolean WithNoCheck { get; set; } /// /// Indica el tipo de constraint (PrimaryKey, ForeignKey, Unique o Default). /// public ConstraintType Type { get; set; } /// /// ID de la tabla relacionada a la que hace referencia (solo aplica a FK) /// public int RelationalTableId { get; set; } /// /// Nombre de la tabla relacionada a la que hace referencia (solo aplica a FK) /// public string RelationalTableFullName { get; set; } /// /// Compara dos campos y devuelve true si son iguales, caso contrario, devuelve false. /// public static Boolean Compare(Constraint origin, Constraint destination) { if (destination == null) throw new ArgumentNullException("destination"); if (origin == null) throw new ArgumentNullException("origin"); if (origin.NotForReplication != destination.NotForReplication) return false; if ((origin.RelationalTableFullName == null) && (destination.RelationalTableFullName != null)) return false; if (origin.RelationalTableFullName != null) if (!origin.RelationalTableFullName.Equals(destination.RelationalTableFullName, StringComparison.CurrentCultureIgnoreCase)) return false; if ((origin.Definition == null) && (destination.Definition != null)) return false; if (origin.Definition != null) if ((!origin.Definition.Equals(destination.Definition)) && (!origin.Definition.Equals("(" + destination.Definition + ")"))) return false; /*Solo si la constraint esta habilitada, se chequea el is_trusted*/ if (!destination.IsDisabled) if (origin.WithNoCheck != destination.WithNoCheck) return false; if (origin.OnUpdateCascade != destination.OnUpdateCascade) return false; if (origin.OnDeleteCascade != destination.OnDeleteCascade) return false; if (!ConstraintColumns.Compare(origin.Columns, destination.Columns)) return false; if ((origin.Index != null) && (destination.Index != null)) return Index.Compare(origin.Index, destination.Index); return true; } private string ToSQLGeneric(ConstraintType consType) { Database database = null; ISchemaBase current = this; while (database == null && current.Parent != null) { database = current.Parent as Database; current = current.Parent; } var isAzure10 = database.Info.Version == DatabaseInfo.SQLServerVersion.SQLServerAzure10; string typeConstraint = ""; StringBuilder sql = new StringBuilder(); if (Parent.ObjectType != ObjectType.TableType) sql.Append("CONSTRAINT [" + Name + "] "); else sql.Append("\t"); if (consType == ConstraintType.PrimaryKey) sql.Append("PRIMARY KEY"); else sql.Append("UNIQUE"); if (Index != null) { sql.Append(" "); sql.Append(Index.Type.ToString().ToUpperInvariant()); } sql.Append("\r\n\t(\r\n"); this.Columns.Sort(); for (int j = 0; j < this.Columns.Count; j++) { sql.Append("\t\t[" + this.Columns[j].Name + "]"); if (this.Columns[j].Order) sql.Append(" DESC"); else sql.Append(" ASC"); if (j != this.Columns.Count - 1) sql.Append(","); sql.AppendLine(); } sql.Append("\t)"); if (Index != null) { sql.Append(" WITH ("); if (Parent.ObjectType == ObjectType.TableType) if (Index.IgnoreDupKey) sql.Append("IGNORE_DUP_KEY = ON"); else sql.Append("IGNORE_DUP_KEY = OFF"); else { if (!isAzure10) { if (Index.IsPadded) sql.Append("PAD_INDEX = ON, "); else sql.Append("PAD_INDEX = OFF, "); } if (Index.IsAutoStatistics) sql.Append("STATISTICS_NORECOMPUTE = ON"); else sql.Append("STATISTICS_NORECOMPUTE = OFF"); if (Index.IgnoreDupKey) sql.Append(", IGNORE_DUP_KEY = ON"); else sql.Append(", IGNORE_DUP_KEY = OFF"); if (!isAzure10) { if (Index.AllowRowLocks) sql.Append(", ALLOW_ROW_LOCKS = ON"); else sql.Append(", ALLOW_ROW_LOCKS = OFF"); if (Index.AllowPageLocks) sql.Append(", ALLOW_PAGE_LOCKS = ON"); else sql.Append(", ALLOW_PAGE_LOCKS = OFF"); if (Index.FillFactor != 0) sql.Append(", FILLFACTOR = " + Index.FillFactor.ToString(CultureInfo.InvariantCulture)); } } sql.Append(")"); if (!isAzure10) { if (!String.IsNullOrEmpty(Index.FileGroup)) sql.Append(" ON [" + Index.FileGroup + "]"); } } return sql.ToString(); } /// /// Devuelve el schema de la tabla en formato SQL. /// public override string ToSql() { if (this.Type == ConstraintType.PrimaryKey) { return ToSQLGeneric(ConstraintType.PrimaryKey); } if (this.Type == ConstraintType.ForeignKey) { StringBuilder sql = new StringBuilder(); StringBuilder sqlReference = new StringBuilder(); int indexc = 0; this.Columns.Sort(); sql.Append("CONSTRAINT [" + Name + "] FOREIGN KEY\r\n\t(\r\n"); foreach (ConstraintColumn column in this.Columns) { sql.Append("\t\t[" + column.Name + "]"); sqlReference.Append("\t\t[" + column.ColumnRelationalName + "]"); if (indexc != this.Columns.Count - 1) { sql.Append(","); sqlReference.Append(","); } sql.AppendLine(); sqlReference.AppendLine(); indexc++; } sql.Append("\t)\r\n"); sql.Append("\tREFERENCES " + this.RelationalTableFullName + "\r\n\t(\r\n"); sql.Append(sqlReference + "\t)"); if (OnUpdateCascade == 1) sql.Append(" ON UPDATE CASCADE"); if (OnDeleteCascade == 1) sql.Append(" ON DELETE CASCADE"); if (OnUpdateCascade == 2) sql.Append(" ON UPDATE SET NULL"); if (OnDeleteCascade == 2) sql.Append(" ON DELETE SET NULL"); if (OnUpdateCascade == 3) sql.Append(" ON UPDATE SET DEFAULT"); if (OnDeleteCascade == 3) sql.Append(" ON DELETE SET DEFAULT"); sql.Append((NotForReplication ? " NOT FOR REPLICATION" : "")); return sql.ToString(); } if (this.Type == ConstraintType.Unique) { return ToSQLGeneric(ConstraintType.Unique); } if (this.Type == ConstraintType.Check) { string sqlcheck = ""; if (Parent.ObjectType != ObjectType.TableType) sqlcheck = "CONSTRAINT [" + Name + "] "; return sqlcheck + "CHECK " + (NotForReplication ? "NOT FOR REPLICATION" : "") + " (" + Definition + ")"; } return ""; } public override string ToSqlAdd() { return "ALTER TABLE " + Parent.FullName + (WithNoCheck ? " WITH NOCHECK" : "") + " ADD " + ToSql() + "\r\nGO\r\n"; } public override string ToSqlDrop() { return ToSqlDrop(null); } public override SQLScript Create() { ScriptAction action = ScriptAction.AddConstraint; if (this.Type == ConstraintType.ForeignKey) action = ScriptAction.AddConstraintFK; if (this.Type == ConstraintType.PrimaryKey) action = ScriptAction.AddConstraintPK; if (!GetWasInsertInDiffList(action)) { SetWasInsertInDiffList(action); return new SQLScript(this.ToSqlAdd(), ((Table)Parent).DependenciesCount, action); } else return null; } public override SQLScript Drop() { ScriptAction action = ScriptAction.DropConstraint; if (this.Type == ConstraintType.ForeignKey) action = ScriptAction.DropConstraintFK; if (this.Type == ConstraintType.PrimaryKey) action = ScriptAction.DropConstraintPK; if (!GetWasInsertInDiffList(action)) { SetWasInsertInDiffList(action); return new SQLScript(this.ToSqlDrop(), ((Table)Parent).DependenciesCount, action); } else return null; } public string ToSqlDrop(string FileGroupName) { string sql = "ALTER TABLE " + ((Table)Parent).FullName + " DROP CONSTRAINT [" + Name + "]"; if (!String.IsNullOrEmpty(FileGroupName)) sql += " WITH (MOVE TO [" + FileGroupName + "])"; sql += "\r\nGO\r\n"; return sql; } public string ToSQLEnabledDisabled() { StringBuilder sql = new StringBuilder(); if (this.IsDisabled) return "ALTER TABLE " + Parent.FullName + " NOCHECK CONSTRAINT [" + Name + "]\r\nGO\r\n"; else { return "ALTER TABLE " + Parent.FullName + " CHECK CONSTRAINT [" + Name + "]\r\nGO\r\n"; } } public override SQLScriptList ToSqlDiff(System.Collections.Generic.ICollection schemas) { SQLScriptList list = new SQLScriptList(); if (this.Status != ObjectStatus.Original) RootParent.ActionMessage[Parent.FullName].Add(this); if (this.HasState(ObjectStatus.Drop)) { if (this.Parent.Status != ObjectStatus.Rebuild) list.Add(Drop()); } if (this.HasState(ObjectStatus.Create)) list.Add(Create()); if (this.HasState(ObjectStatus.Alter)) { list.Add(Drop()); list.Add(Create()); } if (this.HasState(ObjectStatus.Disabled)) { list.Add(this.ToSQLEnabledDisabled(), ((Table)Parent).DependenciesCount, ScriptAction.AlterConstraint); } /*if (this.Status == StatusEnum.ObjectStatusType.ChangeFileGroup) { list.Add(this.ToSQLDrop(this.Index.FileGroup), ((Table)Parent).DependenciesCount, actionDrop); list.Add(this.ToSQLAdd(), ((Table)Parent).DependenciesCount, actionAdd); }*/ return list; } } }