• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HicServices / RDMP / 6237307473

19 Sep 2023 04:02PM UTC coverage: 57.015% (-0.4%) from 57.44%
6237307473

push

github

web-flow
Feature/rc4 (#1570)

* Syntax tidying
* Dependency updates
* Event handling singletons (ThrowImmediately and co)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: James A Sutherland <>
Co-authored-by: James Friel <jfriel001@dundee.ac.uk>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

10734 of 20259 branches covered (0.0%)

Branch coverage included in aggregate %.

5922 of 5922 new or added lines in 565 files covered. (100.0%)

30687 of 52390 relevant lines covered (58.57%)

7361.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.0
/Rdmp.Core/Sharing/Refactoring/SelectSQLRefactorer.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System.Collections.Generic;
8
using System.Linq;
9
using System.Text.RegularExpressions;
10
using FAnsi.Naming;
11
using Rdmp.Core.Curation.Data;
12
using Rdmp.Core.MapsDirectlyToDatabaseTable;
13
using Rdmp.Core.QueryBuilding;
14
using Rdmp.Core.Sharing.Refactoring.Exceptions;
15

16
namespace Rdmp.Core.Sharing.Refactoring;
17

18
/// <summary>
19
/// Handles making changes to SelectSQL properties that revolve around changing which underlying table/column drives the SQL.  For example when a user
20
/// renames a TableInfo and wants to refactor the changes into all the ColumnInfos that underly it and all the ExtractionInformations that come from
21
/// those ColumnInfos and then all the CohortIdentificationConfigurations, AggregateConfigurations etc etc.
22
/// </summary>
23
public class SelectSQLRefactorer
24
{
25
    /// <summary>
26
    /// Replaces all references to the given table with the new table name in a columns SelectSQL.  This will also save the column.  Ensure
27
    /// that new tableName is in fact fully qualified e.g. '[db]..[tbl]'
28
    /// </summary>
29
    /// <param name="column"></param>
30
    /// <param name="tableName"></param>
31
    /// <param name="newFullySpecifiedTableName"></param>
32
    public static void RefactorTableName(IColumn column, IHasFullyQualifiedNameToo tableName,
33
        string newFullySpecifiedTableName)
34
    {
35
        if (column.ColumnInfo == null)
12!
36
            throw new RefactoringException($"Cannot refactor '{column}' because its ColumnInfo was null");
×
37

38
        var fullyQualifiedName = tableName.GetFullyQualifiedName();
12✔
39
        if (!column.SelectSQL.Contains(fullyQualifiedName))
12✔
40
            throw new RefactoringException(
4✔
41
                $"IColumn '{column}' did not contain the fully specified table name during refactoring ('{fullyQualifiedName}'");
4✔
42

43
        if (!newFullySpecifiedTableName.Contains('.'))
8!
44
            throw new RefactoringException(
×
45
                $"Replacement table name was not fully specified, value passed was '{newFullySpecifiedTableName}' which did not contain any dots");
×
46

47
        column.SelectSQL = column.SelectSQL.Replace(fullyQualifiedName, newFullySpecifiedTableName);
8✔
48
        Save(column);
8✔
49
    }
8✔
50

51
    /// <summary>
52
    /// Replaces all references to the given table with the new table name in a ColumnInfo.  This will also save the column.    Ensure
53
    /// that new tableName is in fact fully qualified e.g. '[db]..[tbl]'
54
    /// </summary>
55
    /// <param name="column"></param>
56
    /// <param name="tableName"></param>
57
    /// <param name="newFullySpecifiedTableName"></param>
58
    public static void RefactorTableName(ColumnInfo column, IHasFullyQualifiedNameToo tableName,
59
        string newFullySpecifiedTableName)
60
    {
61
        var fullyQualifiedName = tableName.GetFullyQualifiedName();
2✔
62

63
        if (!column.Name.StartsWith(fullyQualifiedName))
2!
64
            throw new RefactoringException(
×
65
                $"ColumnInfo '{column}' did not start with the fully specified table name during refactoring ('{fullyQualifiedName}'");
×
66

67
        if (!newFullySpecifiedTableName.Contains('.'))
2!
68
            throw new RefactoringException(
×
69
                $"Replacement table name was not fully specified, value passed was '{newFullySpecifiedTableName}' which did not contain any dots");
×
70

71
        column.Name = column.Name.Replace(fullyQualifiedName, newFullySpecifiedTableName);
2✔
72
        column.SaveToDatabase();
2✔
73
    }
2✔
74

75
    protected static void Save(object o)
76
    {
77
        if (o is ISaveable s)
208✔
78
            s.SaveToDatabase();
208✔
79
    }
208✔
80

81
    /// <summary>
82
    /// Replaces all references to the given table with the new table name in a columns SelectSQL.  This will also save the column.  Ensure
83
    /// that newFullySpecifiedColumnName is in fact fully qualified too e.g. [mydb]..[mytable].[mycol]
84
    /// </summary>
85
    /// <param name="column"></param>
86
    /// <param name="columnName"></param>
87
    /// <param name="newFullySpecifiedColumnName"></param>
88
    /// <param name="strict">Determines behaviour when column SelectSQL does not contain a reference to columnName.  True will throw a RefactoringException, false will return without making any changes</param>
89
    public static void RefactorColumnName(IColumn column, IHasFullyQualifiedNameToo columnName,
90
        string newFullySpecifiedColumnName, bool strict = true)
91
    {
92
        var fullyQualifiedName = columnName.GetFullyQualifiedName();
6,318✔
93

94
        if (!column.SelectSQL.Contains(fullyQualifiedName))
6,318✔
95
            if (strict)
6,120!
96
                throw new RefactoringException(
×
97
                    $"IColumn '{column}' did not contain the fully specified column name during refactoring ('{fullyQualifiedName}'");
×
98
            else
99
                return;
6,120✔
100

101
        if (newFullySpecifiedColumnName.Count(c => c == '.') < 2)
11,548!
102
            throw new RefactoringException(
×
103
                $"Replacement column name was not fully specified, value passed was '{newFullySpecifiedColumnName}' which should have had at least 2 dots");
×
104

105
        column.SelectSQL = column.SelectSQL.Replace(fullyQualifiedName, newFullySpecifiedColumnName);
198✔
106

107
        Save(column);
198✔
108
    }
198✔
109

110
    /// <summary>
111
    /// Determines whether the SelectSQL of the specified IColumn includes fully specified refactorable references.  If the SelectSQL is properly
112
    /// formed then the underlying column should appear fully specified at least once in the SelectSQL e.g. UPPER([db]..[tbl].[col])
113
    /// </summary>
114
    /// <param name="column"></param>
115
    /// <returns></returns>
116
    public static bool IsRefactorable(IColumn column) => GetReasonNotRefactorable(column) == null;
500✔
117

118
    /// <summary>
119
    /// Determines whether the SelectSQL of the specified IColumn includes fully specified refactorable references.  Returns the reason why
120
    /// the IColumn is not IsRefactorable (or null if it is).
121
    /// </summary>
122
    /// <param name="column"></param>
123
    /// <returns></returns>
124
    public static string GetReasonNotRefactorable(IColumn column)
125
    {
126
        var ci = column.ColumnInfo;
502✔
127

128
        if (ci == null)
502!
129
            return $"Cannot refactor '{column}' because its ColumnInfo was null";
×
130

131
        if (!column.SelectSQL.Contains(ci.Name))
502✔
132
            return
8✔
133
                $"IColumn '{column}' did not contain the fully specified column name of its underlying ColumnInfo ('{ci.Name}') during refactoring";
8✔
134

135
        var fullyQualifiedName = ci.TableInfo.GetFullyQualifiedName();
494✔
136

137
        return !column.SelectSQL.Contains(fullyQualifiedName)
494!
138
            ? $"IColumn '{column}' did not contain the fully specified table name ('{fullyQualifiedName}') during refactoring"
494✔
139
            : null;
494✔
140
    }
141

142
    /// <summary>
143
    /// Returns true if the <paramref name="tableInfo"/> supports refactoring (e.g. renaming). See <see cref="GetReasonNotRefactorable(ITableInfo)"/>
144
    /// for the reason why
145
    /// </summary>
146
    /// <param name="tableInfo"></param>
147
    /// <returns></returns>
148
    public static bool IsRefactorable(ITableInfo tableInfo) => GetReasonNotRefactorable(tableInfo) == null;
8✔
149

150
    /// <summary>
151
    /// Returns the reason why <paramref name="table"/> is not refactorable e.g. if its name is not properly qualified
152
    /// with database.  Returns null if it is refactorable
153
    /// </summary>
154
    /// <param name="table"></param>
155
    /// <returns></returns>
156
    public static string GetReasonNotRefactorable(ITableInfo table)
157
    {
158
        if (string.IsNullOrWhiteSpace(table.Name))
10!
159
            return "Table has no Name property, this should be the fully qualified database table name";
×
160

161
        if (string.IsNullOrWhiteSpace(table.Database))
10!
162
            return "Table does not have its Database property set";
×
163

164
        //ensure database and Name match correctly
165
        var syntaxHelper = table.GetQuerySyntaxHelper();
10✔
166
        var db = table.GetDatabaseRuntimeName(Curation.Data.DataLoad.LoadStage.PostLoad);
10✔
167

168
        if (!table.Name.StartsWith(syntaxHelper.EnsureWrapped(db)))
10✔
169
            return $"Table with Name '{table.Name}' has incorrect database property '{table.Database}'";
6✔
170

171
        return table.Name != table.GetFullyQualifiedName()
4!
172
            ? $"Table name '{table.Name}' did not match the expected fully qualified name '{table.GetFullyQualifiedName()}'"
4✔
173
            : null;
4✔
174
    }
175

176
    /// <summary>
177
    /// Changes the name of the <paramref name="tableInfo"/> to a new name (which must be fully qualified). This will also
178
    /// update any <see cref="ColumnInfo"/> and <see cref="ExtractionInformation"/> objects declared against the <paramref name="tableInfo"/>.
179
    /// 
180
    /// <para>If you have transforms etc in <see cref="ExtractionInformation"/> then these may not be successfully refactored</para>
181
    /// </summary>
182
    /// <param name="tableInfo"></param>
183
    /// <param name="newFullyQualifiedTableName"></param>
184
    /// <returns>Total number of changes made in columns and table name</returns>
185
    public static int RefactorTableName(ITableInfo tableInfo, string newFullyQualifiedTableName) =>
186
        RefactorTableName(tableInfo, tableInfo.GetFullyQualifiedName(), newFullyQualifiedTableName);
4✔
187

188
    /// <inheritdoc cref="RefactorTableName(ITableInfo, string)"/>
189
    public static int RefactorTableName(ITableInfo tableInfo, string oldFullyQualifiedTableName,
190
        string newFullyQualifiedTableName)
191
    {
192
        if (!IsRefactorable(tableInfo))
4✔
193
            throw new RefactoringException(
2✔
194
                $"TableInfo {tableInfo} is not refactorable because {GetReasonNotRefactorable(tableInfo)}");
2✔
195

196
        var updatesMade = 0;
2✔
197

198
        //if it's a new name
199
        if (tableInfo.Name != newFullyQualifiedTableName)
2✔
200
        {
201
            tableInfo.Name = newFullyQualifiedTableName;
2✔
202
            Save(tableInfo);
2✔
203
            updatesMade++;
2✔
204
        }
205

206
        //Rename all ColumnInfos that belong to this TableInfo
207
        foreach (var columnInfo in tableInfo.ColumnInfos)
4!
208
            updatesMade += RefactorTableName(columnInfo, oldFullyQualifiedTableName, newFullyQualifiedTableName);
×
209

210
        return updatesMade;
2✔
211
    }
212

213
    /// <summary>
214
    /// Replaces the <paramref name="oldFullyQualifiedTableName"/> with the <paramref name="newFullyQualifiedTableName"/> in the
215
    /// given <paramref name="columnInfo"/> and any <see cref="ExtractionInformation"/> declared against it.
216
    /// </summary>
217
    /// <param name="columnInfo"></param>
218
    /// <param name="oldFullyQualifiedTableName"></param>
219
    /// <param name="newFullyQualifiedTableName"></param>
220
    /// <returns>Total number of changes made including <paramref name="columnInfo"/> and any <see cref="ExtractionInformation"/> declared on it</returns>
221
    public static int RefactorTableName(ColumnInfo columnInfo, string oldFullyQualifiedTableName,
222
        string newFullyQualifiedTableName)
223
    {
224
        var updatesMade = 0;
14✔
225

226
        //run what they asked for
227
        updatesMade += RefactorTableNameImpl(columnInfo, oldFullyQualifiedTableName, newFullyQualifiedTableName);
14✔
228

229
        //these are all the things that could appear spattered throughout the old columns
230
        var oldPrefixes = new List<string> { "..", ".dbo.", ".[dbo]." };
14✔
231

232
        //this is what they said they wanted in the refactoring
233
        var newPrefix = oldPrefixes.FirstOrDefault(newFullyQualifiedTableName.Contains);
14✔
234

235
        //if they are trying to standardise
236
        if (newPrefix != null)
14✔
237
            foreach (var old in oldPrefixes)
112✔
238
                if (!string.Equals(old, newPrefix))
42✔
239
                    updatesMade += RefactorTableNameImpl(columnInfo, oldFullyQualifiedTableName.Replace(newPrefix, old),
28✔
240
                        newFullyQualifiedTableName);
28✔
241

242
        return updatesMade;
14✔
243
    }
244

245
    private static int RefactorTableNameImpl(ColumnInfo columnInfo, string oldFullyQualifiedTableName,
246
        string newFullyQualifiedTableName)
247
    {
248
        var updatesMade = 0;
42✔
249

250
        var extractionInformations = columnInfo.ExtractionInformations.ToArray();
42✔
251

252
        foreach (var extractionInformation in extractionInformations)
84!
253
            if (extractionInformation.SelectSQL.Contains(oldFullyQualifiedTableName))
×
254
            {
255
                var newvalue =
×
256
                    extractionInformation.SelectSQL.Replace(oldFullyQualifiedTableName, newFullyQualifiedTableName);
×
257

258
                if (!extractionInformation.SelectSQL.Equals(newvalue))
×
259
                {
260
                    extractionInformation.SelectSQL = newvalue;
×
261
                    extractionInformation.SaveToDatabase();
×
262
                    updatesMade++;
×
263
                }
264
            }
265

266
        //rename ColumnInfos
267
        if (columnInfo.Name.StartsWith(oldFullyQualifiedTableName))
42✔
268
        {
269
            columnInfo.Name = Regex.Replace(columnInfo.Name, $"^{Regex.Escape(oldFullyQualifiedTableName)}",
14✔
270
                newFullyQualifiedTableName);
14✔
271
            columnInfo.SaveToDatabase();
14✔
272
            updatesMade++;
14✔
273
        }
274

275
        return updatesMade;
42✔
276
    }
277
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc