• 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

81.13
/Rdmp.Core/DataLoad/Modules/Mutilators/SafePrimaryKeyCollisionResolverMutilation.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.Linq;
8
using FAnsi.Discovery;
9
using Rdmp.Core.Curation.Data;
10
using Rdmp.Core.Curation.Data.DataLoad;
11
using Rdmp.Core.DataLoad.Engine.Job;
12
using Rdmp.Core.DataLoad.Engine.Mutilators;
13
using Rdmp.Core.ReusableLibraryCode.Checks;
14
using Rdmp.Core.ReusableLibraryCode.Progress;
15

16
namespace Rdmp.Core.DataLoad.Modules.Mutilators;
17

18
/// <summary>
19
/// Attempts to resolves primary key collisions by ordering on a specific column and deleting rows which differ on this column
20
/// </summary>
21
public class SafePrimaryKeyCollisionResolverMutilation : IPluginMutilateDataTables
22
{
23
    private DiscoveredDatabase _database;
24
    private LoadStage _loadStage;
25

26
    [DemandsInitialization(
27
        "The non primary key column to be used for deduplication.  This must contain different values for the same primary key and you must only want to keep one e.g. DataAge")]
28
    public ColumnInfo ColumnToResolveOn { get; set; }
228✔
29

30
    [DemandsInitialization(
31
        @"Determines behaviour when a primary key collision is the result of one record having null and another not.
32
True - Delete the non null record
33
False - Delete the null record")]
34
    public bool PreferNulls { get; set; }
92✔
35

36
    [DemandsInitialization(@"Determines which record is deleted when two values of ColumnToResolveOn are conflicting
37
True - Delete the smaller value
38
False - Delete the larger value")]
39
    public bool PreferLargerValues { get; set; }
92✔
40

41
    [DemandsInitialization("Timeout in seconds to allow the operation to run for", DefaultValue = 600)]
42
    public int Timeout { get; set; }
48✔
43

44
    public void DeleteRows(DiscoveredTable tbl, ColumnInfo[] primaryKeys, IDataLoadEventListener listener)
45
    {
46
        var join = string.Join(" AND ", primaryKeys.Select(k => $"t1.{k.GetRuntimeName()}=t2.{k.GetRuntimeName()}"));
96✔
47

48

49
        var t1DotColumn = $"t1.{ColumnToResolveOn.GetRuntimeName(_loadStage)}";
44✔
50
        var t2DotColumn = $"t2.{ColumnToResolveOn.GetRuntimeName(_loadStage)}";
44✔
51

52
        //FYI - we are considering whether to delete records from table {0}
53

54
        var deleteConditional =
44✔
55
            PreferNulls
44✔
56
                ?
44✔
57
                //delete rows {0} where {0} is not null and {1} is null - leaving only the null records {1}
44✔
58
                $"({t1DotColumn} IS NOT NULL AND {t2DotColumn} IS NULL)"
44✔
59
                : string.Format("({1} IS NOT NULL AND {0} IS NULL)", t1DotColumn, t2DotColumn);
44✔
60

61
        deleteConditional += " OR ";
44✔
62

63
        deleteConditional += PreferLargerValues
44✔
64
            //delete rows {0} where {0} less than {1} - leaving only the greater records {1}
44✔
65
            ? string.Format("({0} <> {1} AND {0} < {1})", t1DotColumn, t2DotColumn)
44✔
66
            : string.Format("({0} <> {1} AND {1} < {0})", t1DotColumn, t2DotColumn);
44✔
67

68

69
        var sql = string.Format(@"DELETE t1 FROM {0} t1
44✔
70
  JOIN {0} t2
44✔
71
  ON {1}
44✔
72
  AND ({2})",
44✔
73
            tbl.GetRuntimeName(), join, deleteConditional);
44✔
74

75
        using var con = tbl.Database.Server.GetConnection();
44✔
76
        con.Open();
44✔
77
        using var cmd = tbl.Database.Server.GetCommand(sql, con);
44✔
78
        cmd.CommandTimeout = Timeout;
44✔
79

80
        var affectedRows = cmd.ExecuteNonQuery();
44✔
81
        listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Information,
44✔
82
            $"Deleted {affectedRows} rows"));
44✔
83
    }
88✔
84

85
    public void Check(ICheckNotifier notifier)
86
    {
87
        if (ColumnToResolveOn is { IsPrimaryKey: true })
×
88
            notifier.OnCheckPerformed(new CheckEventArgs(
×
89
                $"You cannot use {ColumnToResolveOn} to resolve primary key collisions because it is part of the primary key",
×
90
                CheckResult.Fail));
×
91
    }
×
92

93
    public void LoadCompletedSoDispose(ExitCodeType exitCode, IDataLoadEventListener postLoadEventsListener)
94
    {
95
    }
×
96

97
    public void Initialize(DiscoveredDatabase dbInfo, LoadStage loadStage)
98
    {
99
        _database = dbInfo;
48✔
100
        _loadStage = loadStage;
48✔
101
    }
48✔
102

103
    public ExitCodeType Mutilate(IDataLoadJob job)
104
    {
105
        var tbl = _database.ExpectTable(
44✔
106
            ColumnToResolveOn.TableInfo.GetRuntimeName(_loadStage, job.Configuration.DatabaseNamer));
44✔
107
        var pks = ColumnToResolveOn.TableInfo.ColumnInfos.Where(ci => ci.IsPrimaryKey).ToArray();
184✔
108

109
        DeleteRows(tbl, pks, job);
44✔
110

111
        return ExitCodeType.Success;
44✔
112
    }
113
}
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