diff --git a/SQL Format/FormSQLFormat.cs b/SQL Format/FormSQLFormat.cs index 4b9b686..6c9cb1f 100644 --- a/SQL Format/FormSQLFormat.cs +++ b/SQL Format/FormSQLFormat.cs @@ -25,13 +25,17 @@ public FormSQLFormat() AddItemByClass(new SQLTranslatorUpdate()); AddItemByClass(new SQLTranslatorMerge()); AddItemByClass(new SQLTranslatorCopy()); + AddItemByClass(new SQLTranslatorCopySingle()); AddItemByClass(new SQLTranslatorInsert()); AddItemByClass(new SQLTranslatorSame()); AddItemByClass(new SQLTranslatorXmlSelect()); - AddItemByClass(new TabTranslatorJson()); + //AddItemByClass(new TabTranslatorJson()); +#if DEBUG + AddItemByClass(new SQLTranslatorMyScript()); +#endif } - void AddItemByClass(SQLTranslator t) + void AddItemByClass(SQLTranslator t) { int idx = TabCtrl.TabPages.Count; TabCtrl.TabPages.Add(t.GetCaption()); diff --git a/SQL Format/Helpers/SqlBuilder.cs b/SQL Format/Helpers/SqlBuilder.cs index 0afa764..5d548d3 100644 --- a/SQL Format/Helpers/SqlBuilder.cs +++ b/SQL Format/Helpers/SqlBuilder.cs @@ -91,7 +91,7 @@ public SqlBuilder AppendIf(string lineCondition) public SqlBuilder AppendCatchTypicalRollback() { AppendBegin(BEGIN_CATCH); - AppendLine($"if @@trancount > 0 rollback;"); + AppendLine($"if @@trancount > 0 begin print('rollback'); rollback; end"); AppendLine($";throw;"); AppendEnd(BEGIN_END); return this; diff --git a/SQL Format/Helpers/SqlDomBuilder.cs b/SQL Format/Helpers/SqlDomBuilder.cs index 598dba5..f15367e 100644 --- a/SQL Format/Helpers/SqlDomBuilder.cs +++ b/SQL Format/Helpers/SqlDomBuilder.cs @@ -45,6 +45,31 @@ public void ProduceFullTableRenameScript(TSqlScript script, string suffix, bool objType = OBJECT; _sqlBuilder.AppendSpRename(objName, newName, objType, "constraint").NL(); } + + foreach (var colDef in createTableStatement.Definition.ColumnDefinitions) + { + foreach (var constraint in colDef.Constraints) + { + if (constraint.ConstraintIdentifier == null) continue; + var conName = TSQLHelper.Identifier2Value(constraint.ConstraintIdentifier); + objName = !reverse ? $"{schemaName}.{conName}" : $"{schemaName}.{conName + suffix}"; + newName = reverse ? conName : conName + suffix; + objType = OBJECT; + _sqlBuilder.AppendSpRename(objName, newName, objType, "constraint").NL(); + } + + { + var constraint = colDef.DefaultConstraint; + if (constraint == null || constraint.ConstraintIdentifier == null) continue; + var conName = TSQLHelper.Identifier2Value(constraint.ConstraintIdentifier); + objName = !reverse ? $"{schemaName}.{conName}" : $"{schemaName}.{conName + suffix}"; + newName = reverse ? conName : conName + suffix; + objType = OBJECT; + _sqlBuilder.AppendSpRename(objName, newName, objType, "constraint").NL(); + } + + } + } if (statement is CreateIndexStatement createIndexStatement) { @@ -382,5 +407,93 @@ public void ProduceCopytableVerify(CreateTableStatement createTableStatement, st Print($"Success comparing {sourceTableNameFull} with {destTableNameFull}"); _sqlBuilder.Unindent(); } + + public void ProduceCopyTableSingle(CreateTableStatement createTableStatement, string varSuffix, string targetTableNameFull = null) + { + string sourceTableNameFull = TSQLHelper.Identifiers2Value(createTableStatement.SchemaObjectName.Identifiers); + string destTableNameFull = targetTableNameFull != null ? targetTableNameFull : TSQLHelper.Identifiers2Value(createTableStatement.SchemaObjectName.Identifiers); + + UniqueConstraintDefinition clusteredKey = createTableStatement.Definition.TableConstraints.Where(c => c is UniqueConstraintDefinition uni && uni.Clustered == true).SingleOrDefault() as UniqueConstraintDefinition; + if (clusteredKey is null) + { + _sqlBuilder.AppendLine("Error: No clustered index!"); + return; + } + ColInfo keyColumn = new ColInfo(); + { + var colName = TSQLHelper.Identifiers2Value(clusteredKey.Columns[0].Column.MultiPartIdentifier.Identifiers); + ColumnDefinition column = createTableStatement.Definition.ColumnDefinitions.Where(c => TSQLHelper.Identifier2Value(c.ColumnIdentifier).ToLowerInvariant() == colName.ToLowerInvariant()).Single(); + keyColumn.Index = 0; + keyColumn.ColumnName = colName; + keyColumn.ColumnDefinition = column; + keyColumn.ColumnTypeStr = TSQLHelper.Column2TypeStr(column); + keyColumn.VarNameLastValue = $"@Key{varSuffix}"; + } + + string AffectedIterationName = $"@AffectedIteration{varSuffix}"; + _sqlBuilder.AppendLine($"declare {AffectedIterationName} int;"); + + string numName = $"@i{varSuffix}"; + _sqlBuilder.AppendLine($"declare {numName} int = 0;"); + + string totalCountName = $"@TotalCount{varSuffix}"; + _sqlBuilder.AppendLine($"declare {totalCountName} int = (select count(*) from {sourceTableNameFull} with (nolock));").NL(); + + + // last column via table + string keysTableName = $"#keys{varSuffix}"; + _sqlBuilder.AppendLine($"drop table if exists {keysTableName};") + .AppendLine($"create table {keysTableName}(") + .Indent() + .AppendLine($"{keyColumn.ColumnName} {keyColumn.ColumnTypeStr} not null primary key clustered") + .Unindent() + .AppendLine(");").NL(); + + + _sqlBuilder.AppendLine($"declare {keyColumn.VarNameLastValue} {keyColumn.ColumnTypeStr};"); + + _sqlBuilder.NL(); + _sqlBuilder.AppendLine($"insert into {keysTableName}({keyColumn.ColumnName})"); + _sqlBuilder.AppendLine($"select distinct t.{keyColumn.ColumnName}"); + _sqlBuilder.AppendLine($"from {sourceTableNameFull} t with (nolock);").NL(); + + string loopCountName = $"@LoopCount{varSuffix}"; + _sqlBuilder.AppendLine($"declare {loopCountName} int = @@rowcount;"); + + Print($"concat('Total count:', {totalCountName}, ', loop count: ', {loopCountName}, ' in {sourceTableNameFull}.')", false); + _sqlBuilder.NL(); + + _sqlBuilder.AppendLine($"while 1=1").AppendBegin(); + { + _sqlBuilder.AppendLine($"set {numName} += 1;").NL(); + + _sqlBuilder.AppendLine($"set {keyColumn.VarNameLastValue} = (").Indent() + .AppendLine($"select top (1) t.{keyColumn.ColumnName}") + .AppendLine($"from {keysTableName} t") + .AppendLine($"where {keyColumn.VarNameLastValue} is null or t.{keyColumn.ColumnName} > {keyColumn.VarNameLastValue}") + .AppendLine($"order by 1 asc") + .Unindent().AppendLine(");").NL(); + + _sqlBuilder.AppendLine($"if {keyColumn.VarNameLastValue} is null").AppendBegin().AppendLine("break;").AppendEnd().NL(); + + + Print($"concat('Processing ', {numName},' / ', {loopCountName}, ', {keyColumn.ColumnName}: ', {keyColumn.VarNameLastValue}, ' ...')", false); + + _sqlBuilder.AppendLine($"insert into {destTableNameFull}(") + .Indent() + .AppendLines(createTableStatement.Definition.ColumnDefinitions.Select(c => TSQLHelper.Identifier2Value(c.ColumnIdentifier)), ",") + .Unindent() + .AppendLine(")") + .AppendLine($"select ") + .Indent() + .AppendLines(createTableStatement.Definition.ColumnDefinitions.Select(c => $"t.{TSQLHelper.Identifier2Value(c.ColumnIdentifier)}"), ",") + .Unindent() + .AppendLine($"from {sourceTableNameFull} t") + .AppendLine($"where t.{keyColumn.ColumnName} = {keyColumn.VarNameLastValue};").NL(); + + } + _sqlBuilder.AppendEnd($"end"); + } + } } diff --git a/SQL Format/SQLFormat.csproj b/SQL Format/SQLFormat.csproj index 89bf507..74cb1af 100644 --- a/SQL Format/SQLFormat.csproj +++ b/SQL Format/SQLFormat.csproj @@ -118,6 +118,8 @@ + + diff --git a/SQL Format/SQLTranslatorCopySingle.cs b/SQL Format/SQLTranslatorCopySingle.cs new file mode 100644 index 0000000..2ba6035 --- /dev/null +++ b/SQL Format/SQLTranslatorCopySingle.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.SqlServer.TransactSql.ScriptDom; +using SQL_Format.Helpers; + +namespace SQL_Format +{ + public class SQLTranslatorCopySingle : SQLTranslator + { + public override string GetCaption() + { + return "Copy Table Sinlge"; + } + + public override void SetupOptionsContent(Control Parent, EventHandler changedHandler) + { + this.AddOptionTextBox("Var Suffix", "varSuffix", "11245U1", Parent, changedHandler); + this.AddOptionCheckBox("Use Transaction", "useTransaction", true, Parent, changedHandler); + this.AddOptionCheckBox("Rollback", "useRollback", true, Parent, changedHandler); + } + + public override string TranslateExt2(CreateTableStatement createTableStatement, object options, string content, TSqlScript sqlScript) + { + string varSuffix = this.GetOptionString("varSuffix", options) ?? ""; + bool useTransaction = this.GetOptionBoolDef("useTransaction", options, false); + bool useRollback = this.GetOptionBoolDef("useRollback", options, false); + + SqlBuilder result = new SqlBuilder(); + SqlDomBuilder sqlDomBuilder = new SqlDomBuilder(sqlScript, result); + + + string tableSuffix = "_new"; + result.TableNameFull = TSQLHelper.Identifiers2Value(createTableStatement.SchemaObjectName.Identifiers); + result.TableName = TSQLHelper.Identifiers2ValueLast(createTableStatement.SchemaObjectName.Identifiers); + string tableNameNew = result.TableName + tableSuffix; + string tableNameFullNew = result.TableNameFull + tableSuffix; + { + result.AppendBegin(SqlBuilder.BEGIN_TRY); + { + if (useTransaction) result.AppendLine($"begin tran;").NL(); + + string messageVarName = $"@msg{varSuffix}"; + result.AppendLine($"declare {messageVarName} nvarchar(2024);").NL(); + sqlDomBuilder.VarMsgName = messageVarName; + sqlDomBuilder.Print($"Running Script{varSuffix}.{result.TableNameFull}...").NL(); + + result.AppendIf($"object_id('{tableNameFullNew}') is null").AppendBegin(); + { + result.AppendLine("-- create new table"); + sqlDomBuilder.ProduceFullTableRenameContent(sqlScript, tableSuffix, content); + + result.AppendEnd(); + } + + sqlDomBuilder.ProduceCopyTableSingle(createTableStatement, varSuffix, targetTableNameFull: tableNameFullNew); + + result.AppendBegin(); + { + result.AppendLine("-- rename source table to _old"); + sqlDomBuilder.ProduceFullTableRenameScript(sqlScript, "_old"); + result.AppendEnd(); + } + + result.AppendBegin(); + { + result.AppendLine("-- rename new table to current"); + sqlDomBuilder.ProduceFullTableRenameScript(sqlScript, tableSuffix, reverse: true); + + result.AppendEnd(); + } + + sqlDomBuilder.ProduceCopytableVerify(createTableStatement, varSuffix, targetTableNameFull: result.TableNameFull + "_old"); + + result.NL(); + sqlDomBuilder.Print("Dropping table {result.TableNameFull}_old..."); + result.AppendLine($"drop table if exists {result.TableNameFull}_old;").NL(); + + if (useTransaction) + { + if (useRollback) + result.AppendLine($"rollback;").NL(); + else + { + result.AppendLine($"commit;").NL(); + sqlDomBuilder.Print("Committed."); + } + } + + result.AppendEnd(SqlBuilder.END_TRY, false); + } + result.AppendCatchTypicalRollback(); + } + return result.ToString(); + } + } +} diff --git a/SQL Format/SQLTranslatorMyScript.cs b/SQL Format/SQLTranslatorMyScript.cs new file mode 100644 index 0000000..1c94261 --- /dev/null +++ b/SQL Format/SQLTranslatorMyScript.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.SqlServer.TransactSql.ScriptDom; +using SQL_Format.Helpers; + +namespace SQL_Format +{ + public class SQLTranslatorMyScript: SQLTranslator + { + public override string GetCaption() + { + return "My Script"; + } + + public override void SetupOptionsContent(Control Parent, EventHandler changedHandler) + { + } + + public override string TranslateExt2(CreateTableStatement createTableStatement, object options, string content, TSqlScript sqlScript) + { + SqlBuilder result = new SqlBuilder(); + SqlDomBuilder sqlDomBuilder = new SqlDomBuilder(sqlScript, result); + result.TableNameFull = TSQLHelper.Identifiers2Value(createTableStatement.SchemaObjectName.Identifiers); + result.TableName = TSQLHelper.Identifiers2ValueLast(createTableStatement.SchemaObjectName.Identifiers); + + int[] precs = new int[] { 18, 26, 20}; + + result.AppendLine($"----------------- {result.TableNameFull} -----------------"); + result.AppendLine($"raiserror('Altering {result.TableNameFull}...', 10, 1) with nowait;"); + + int addedCount = 0; + foreach (var c in createTableStatement.Definition.ColumnDefinitions) + { + string colType = TSQLHelper.Column2TypeStr(c).ToUpperInvariant(); + if (colType.StartsWith("DECIMAL")) + { + int prec = TSQLHelper.ColumnDecimalPrecision(c); + if (precs.Contains(prec)) + { + addedCount++; + bool isNullable = TSQLHelper.ColumnIsNullable(c); + string null_ = isNullable ? " NULL" : " NOT NULL"; + string colName = TSQLHelper.Identifier2Value(c.ColumnIdentifier); + string subalterColumn = $"column {colName} {colType}{null_}"; + result.AppendIf($"not exists(select * from INFORMATION_SCHEMA.COLUMNS c where c.TABLE_NAME = '{result.TableName}' and c.COLUMN_NAME = '{colName}' and c.NUMERIC_PRECISION = {prec})").AppendBegin(); + { + string alterColumn = $"alter {subalterColumn}"; + result.AppendLine($"raiserror('{alterColumn}...', 10, 1) with nowait;"); + if (isNullable) + result.AppendLine($"update {result.TableNameFull} set {colName} = try_cast({colName} as {colType});"); + result.AppendLine($"alter table {result.TableNameFull}").Indent(); + result.AppendLine($"{alterColumn};").Unindent(); + result.AppendEnd(addSpaceafterwards: false); + } + result.AppendLine("else").Indent(); + { + result.AppendLine($"raiserror('already {subalterColumn}.', 10, 1) with nowait;"); + result.Unindent(); + } + + result.AppendLine("GO"); + } + } + } + result.AppendLine($"----------------------------------"); + return result.ToString(); + } + } +} diff --git a/SQL Format/SQLTranslatorXmlSelect.cs b/SQL Format/SQLTranslatorXmlSelect.cs index bae62c1..7ba7604 100644 --- a/SQL Format/SQLTranslatorXmlSelect.cs +++ b/SQL Format/SQLTranslatorXmlSelect.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -99,9 +100,18 @@ public override void SetupOptionsContent(Control Parent, EventHandler changedHan checkBox.AutoSize = true; Parent.Controls.Add(checkBox); } - } - public override string TranslateExt(CreateTableStatement createTableStatement, object options) + { + CheckBox checkBox = new CheckBox(); + checkBox.Text = "json"; + checkBox.Name = "option_json"; + checkBox.CheckedChanged += changedHandler; + checkBox.AutoSize = true; + Parent.Controls.Add(checkBox); + } + } + + public override string TranslateExt(CreateTableStatement createTableStatement, object options) { TableDefinition tableDefinition = createTableStatement.Definition; string optionSource0 = null; @@ -143,7 +153,9 @@ public override string TranslateExt(CreateTableStatement createTableStatement, o } } - bool option_insert = GetOptionBoolDef("option_insert", options, false); + bool option_json = GetOptionBoolDef("option_json", options, false); + + bool option_insert = GetOptionBoolDef("option_insert", options, false); bool option_identity_insert = GetOptionBoolDef("option_identity_insert", options, false); bool option_tab_path = GetOptionBoolDef("option_tab_path", options, false); bool option_output = GetOptionBoolDef("option_output", options, false); @@ -183,6 +195,7 @@ public override string TranslateExt(CreateTableStatement createTableStatement, o result.Append($"select{Environment.NewLine}"); + List jsonWith = new List(); // rows { sep = null; @@ -196,20 +209,38 @@ public override string TranslateExt(CreateTableStatement createTableStatement, o if (option_ccolumns) { attName = $"c{iter}"; } - if (optionsSafe) + if (!option_json) { - result.Append($"\t{sep}{ident} = case when {optionRowAlias0}.value('@{attName}', 'nvarchar(max)') = \'NULL\' then null else {optionRowAlias0}.value('@{attName}', '{typ}') end{Environment.NewLine}"); + if (optionsSafe) + { + result.Append($"\t{sep}{ident} = case when {optionRowAlias0}.value('@{attName}', 'nvarchar(max)') = \'NULL\' then null else {optionRowAlias0}.value('@{attName}', '{typ}') end{Environment.NewLine}"); + } + else + { + result.Append($"\t{sep}{ident} = {optionRowAlias0}.value('@{attName}', '{typ}'){Environment.NewLine}"); + } } else { - result.Append($"\t{sep}{ident} = {optionRowAlias0}.value('@{attName}', '{typ}'){Environment.NewLine}"); - } + result.Append($"\t{sep}{ident} = {optionRowAlias0}.{attName}{Environment.NewLine}"); + jsonWith.Add($"{sep}{attName} {typ} '$.{attName}'"); + } if (String.IsNullOrEmpty(sep)) sep = ", "; } } - string path = "/root[1]/data[1]/row"; - if (option_tab_path) path = $"/root[1]/{tableName0}/row"; - result.Append($"from @{optionSource0}.nodes('{path}') as t({optionRowAlias0}){Environment.NewLine}"); + if (!option_json) + { + string path = "/root[1]/data[1]/row"; + if (option_tab_path) path = $"/root[1]/{tableName0}/row"; + result.Append($"from @{optionSource0}.nodes('{path}') as t({optionRowAlias0}){Environment.NewLine}"); + } + else + { + result.Append($"from openjson(@json) with ({Environment.NewLine}"); + foreach (var ss in jsonWith) + result.Append($"\t{ss}{Environment.NewLine}"); + result.Append($") {optionRowAlias0}{Environment.NewLine}"); + } if (option_identity_insert) { diff --git a/SQL Format/TSQLHelper.cs b/SQL Format/TSQLHelper.cs index 6fe1ba7..360b0a5 100644 --- a/SQL Format/TSQLHelper.cs +++ b/SQL Format/TSQLHelper.cs @@ -41,11 +41,12 @@ public static string Column2TypeStr(ColumnDefinition columnDefinition) if (columnDefinition.DataType == null) return null; string result = columnDefinition.DataType.Name.Identifiers[0].Value.ToLowerInvariant(); //+ Name {Microsoft.SqlServer.TransactSql.ScriptDom.SchemaObjectName} Microsoft.SqlServer.TransactSql.ScriptDom.SchemaObjectName - if (columnDefinition.DataType is ParameterizedDataTypeReference) + if (columnDefinition.DataType is ParameterizedDataTypeReference p) { - if (((ParameterizedDataTypeReference)columnDefinition.DataType).Parameters.Count > 0) + if (p.Parameters.Count > 0) { - result = $"{result}({((ParameterizedDataTypeReference)columnDefinition.DataType).Parameters[0].Value})"; + var pars = string.Join(", ", p.Parameters.Select(s => s.Value)); + result += $"({pars})"; } } @@ -76,7 +77,24 @@ public static bool ColumnIsIdentity(ColumnDefinition columnDefinition) return false; } - public static bool ColumnIsPrimaryKey(ColumnDefinition columnDefinition, TableDefinition definition, bool AllowUnique = false) + public static bool ColumnIsNullable(ColumnDefinition columnDefinition) + { + return columnDefinition.Constraints.Where(c => c is NullableConstraintDefinition nu && nu.Nullable == true).Any(); + } + + public static int ColumnDecimalPrecision(ColumnDefinition columnDefinition) + { + if (columnDefinition.DataType is ParameterizedDataTypeReference p) + { + if (p.Parameters.Count > 0) + { + return int.Parse(p.Parameters[0].Value); + } + } + return 0; + } + + public static bool ColumnIsPrimaryKey(ColumnDefinition columnDefinition, TableDefinition definition, bool AllowUnique = false) { if ((columnDefinition.Index != null) && (columnDefinition.Index.IndexType != null) && (columnDefinition.Index.IndexType.IndexTypeKind == IndexTypeKind.Clustered)) {