• 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.6
/Rdmp.Core/Curation/Data/DataLoad/LoadMetadata.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;
8
using System.Collections.Generic;
9
using System.Data.Common;
10
using System.Linq;
11
using FAnsi.Discovery;
12
using FAnsi.Discovery.QuerySyntax;
13
using Rdmp.Core.Curation.Data.Cache;
14
using Rdmp.Core.Curation.Data.Defaults;
15
using Rdmp.Core.Curation.Data.ImportExport;
16
using Rdmp.Core.Curation.Data.Serialization;
17
using Rdmp.Core.Logging;
18
using Rdmp.Core.Logging.PastEvents;
19
using Rdmp.Core.MapsDirectlyToDatabaseTable;
20
using Rdmp.Core.MapsDirectlyToDatabaseTable.Attributes;
21
using Rdmp.Core.Repositories;
22
using Rdmp.Core.ReusableLibraryCode;
23
using Rdmp.Core.ReusableLibraryCode.Annotations;
24
using Rdmp.Core.ReusableLibraryCode.DataAccess;
25

26
namespace Rdmp.Core.Curation.Data.DataLoad;
27

28
/// <summary>
29
/// How are files cached within the cache (e.g. within a zip? tar? just uncompressed in a directory).
30
/// </summary>
31
public enum CacheArchiveType
32
{
33
    /// <summary>
34
    /// Cached files are in a directory uncompressed
35
    /// </summary>
36
    None = 0,
37

38
    /// <summary>
39
    /// Cached files are contained in a zip file
40
    /// </summary>
41
    Zip = 1
42
}
43

44
/// <inheritdoc cref="ILoadMetadata"/>
45
public class LoadMetadata : DatabaseEntity, ILoadMetadata, IHasDependencies, IHasQuerySyntaxHelper,
46
    ILoggedActivityRootObject, IHasFolder
47
{
48
    #region Database Properties
49

50
    private string _locationOfFlatFiles;
51
    private string _anonymisationEngineClass;
52
    private string _name;
53
    private string _description;
54
    private CacheArchiveType _cacheArchiveType;
55
    private int? _overrideRawServerID;
56
    private bool _ignoreTrigger;
57
    private string _folder;
58

59
    /// <inheritdoc/>
60
    [AdjustableLocation]
61
    public string LocationOfFlatFiles
62
    {
63
        get => _locationOfFlatFiles;
1,684✔
64
        set => SetField(ref _locationOfFlatFiles, value);
818✔
65
    }
66

67
    /// <summary>
68
    /// Not used
69
    /// </summary>
70
    public string AnonymisationEngineClass
71
    {
72
        get => _anonymisationEngineClass;
670✔
73
        set => SetField(ref _anonymisationEngineClass, value);
700✔
74
    }
75

76
    /// <inheritdoc/>
77
    [Unique]
78
    [NotNull]
79
    public string Name
80
    {
81
        get => _name;
952✔
82
        set => SetField(ref _name, value);
1,326✔
83
    }
84

85
    /// <summary>
86
    /// Human readable description of the load, what it does etc
87
    /// </summary>
88
    public string Description
89
    {
90
        get => _description;
672✔
91
        set => SetField(ref _description, value);
700✔
92
    }
93

94
    /// <summary>
95
    /// The format for storing files in when reading/writing to a cache with a <see cref="CacheProgress"/>.  This may not be respected
96
    /// depending on the implementation of the sepecific ICacheLayout
97
    /// </summary>
98
    public CacheArchiveType CacheArchiveType
99
    {
100
        get => _cacheArchiveType;
670✔
101
        set => SetField(ref _cacheArchiveType, value);
710✔
102
    }
103

104
    /// <summary>
105
    /// Optional.  Indicates that when running the Data Load Engine, the specific <see cref="ExternalDatabaseServer"/> should be used for the RAW server (instead of
106
    /// the system default - see <see cref="ServerDefaults"/>).
107
    /// </summary>
108
    public int? OverrideRAWServer_ID
109
    {
110
        get => _overrideRawServerID;
1,106✔
111
        set => SetField(ref _overrideRawServerID, value);
700✔
112
    }
113

114

115
    /// <iheritdoc/>
116
    public bool IgnoreTrigger
117
    {
118
        get => _ignoreTrigger;
1,008✔
119
        set => SetField(ref _ignoreTrigger, value);
834✔
120
    }
121

122
    /// <inheritdoc/>
123
    [UsefulProperty]
124
    public string Folder
125
    {
126
        get => _folder;
880✔
127
        set => SetField(ref _folder, FolderHelper.Adjust(value));
824✔
128
    }
129

130
    #endregion
131

132

133
    #region Relationships
134

135
    /// <inheritdoc/>
136
    [NoMappingToDatabase]
137
    public ExternalDatabaseServer OverrideRAWServer => OverrideRAWServer_ID.HasValue
332!
138
        ? Repository.GetObjectByID<ExternalDatabaseServer>(OverrideRAWServer_ID.Value)
332✔
139
        : null;
332✔
140

141
    /// <inheritdoc/>
142
    [NoMappingToDatabase]
143
    public ILoadProgress[] LoadProgresses => Repository.GetAllObjectsWithParent<LoadProgress>(this);
200✔
144

145
    /// <inheritdoc/>
146
    [NoMappingToDatabase]
147
    public IOrderedEnumerable<IProcessTask> ProcessTasks
148
    {
149
        get
150
        {
151
            return
264✔
152
                Repository.GetAllObjectsWithParent<ProcessTask>(this).Cast<IProcessTask>().OrderBy(pt => pt.Order);
544✔
153
        }
154
    }
155

156
    #endregion
157

158
    public LoadMetadata()
×
159
    {
160
    }
×
161

162
    /// <summary>
163
    /// Create a new DLE load.  This load will not have any <see cref="ProcessTask"/> and will not load any <see cref="TableInfo"/> yet.
164
    /// 
165
    /// <para>To set the loaded tables, set <see cref="Catalogue.LoadMetadata_ID"/> on some of your datasets</para>
166
    /// </summary>
167
    /// <param name="repository"></param>
168
    /// <param name="name"></param>
169
    public LoadMetadata(ICatalogueRepository repository, string name = null)
310✔
170
    {
171
        name ??= $"NewLoadMetadata{Guid.NewGuid()}";
310✔
172
        repository.InsertAndHydrate(this, new Dictionary<string, object>
310✔
173
        {
310✔
174
            { "Name", name },
310✔
175
            { "IgnoreTrigger", false /*todo could be system global default here*/ },
310✔
176
            { "Folder", FolderHelper.Root }
310✔
177
        });
310✔
178
    }
310✔
179

180
    internal LoadMetadata(ICatalogueRepository repository, DbDataReader r)
181
        : base(repository, r)
502✔
182
    {
183
        LocationOfFlatFiles = r["LocationOfFlatFiles"].ToString();
502✔
184
        Name = r["Name"] as string;
502✔
185
        AnonymisationEngineClass = r["AnonymisationEngineClass"].ToString();
502✔
186
        Name = r["Name"].ToString();
502✔
187
        Description = r["Description"] as string; //allows for nulls
502✔
188
        CacheArchiveType = (CacheArchiveType)r["CacheArchiveType"];
502✔
189
        OverrideRAWServer_ID = ObjectToNullableInt(r["OverrideRAWServer_ID"]);
502✔
190
        IgnoreTrigger = ObjectToNullableBool(r["IgnoreTrigger"]) ?? false;
502✔
191
        Folder = r["Folder"] as string ?? FolderHelper.Root;
502!
192
    }
502✔
193

194
    internal LoadMetadata(ShareManager shareManager, ShareDefinition shareDefinition) : base()
10✔
195
    {
196
        shareManager.UpsertAndHydrate(this, shareDefinition);
10✔
197
    }
10✔
198

199
    /// <inheritdoc/>
200
    public override void DeleteInDatabase()
201
    {
202
        var firstOrDefault = GetAllCatalogues().FirstOrDefault();
74✔
203

204
        if (firstOrDefault != null)
74!
205
            throw new Exception(
×
206
                $"This load is used by {firstOrDefault.Name} so cannot be deleted (Disassociate it first)");
×
207

208
        base.DeleteInDatabase();
74✔
209
    }
74✔
210

211
    /// <inheritdoc/>
212
    public override string ToString() => Name;
94✔
213

214
    /// <inheritdoc/>
215
    public IEnumerable<ICatalogue> GetAllCatalogues() => Repository.GetAllObjectsWithParent<Catalogue>(this);
1,012✔
216

217
    /// <inheritdoc cref="GetDistinctLoggingDatabase()"/>
218
    public DiscoveredServer GetDistinctLoggingDatabase(out IExternalDatabaseServer serverChosen)
219
    {
220
        var loggingServers = GetLoggingServers();
112✔
221

222
        var loggingServer = loggingServers.FirstOrDefault();
112✔
223

224
        //get distinct connection
225
        var toReturn = DataAccessPortal.ExpectDistinctServer(loggingServers, DataAccessContext.Logging, true);
112✔
226

227
        serverChosen = (IExternalDatabaseServer)loggingServer;
106✔
228
        return toReturn;
106✔
229
    }
230

231
    /// <summary>
232
    /// The unique logging server for auditing the load (found by querying <see cref="Catalogue.LiveLoggingServer"/>)
233
    /// </summary>
234
    /// <returns></returns>
235
    public DiscoveredServer GetDistinctLoggingDatabase() => GetDistinctLoggingDatabase(out _);
112✔
236

237
    private IDataAccessPoint[] GetLoggingServers()
238
    {
239
        var catalogue = GetAllCatalogues().ToArray();
112✔
240

241
        return !catalogue.Any()
112!
242
            ? throw new NotSupportedException(
112✔
243
                $"LoadMetaData '{ToString()} (ID={ID}) does not have any Catalogues associated with it so it is not possible to fetch its LoggingDatabaseSettings")
112✔
244
            : (IDataAccessPoint[])catalogue.Select(c => c.LiveLoggingServer).ToArray();
234✔
245
    }
246

247
    /// <summary>
248
    /// Returns the unique value of <see cref="Catalogue.LoggingDataTask"/> amongst all catalogues loaded by the <see cref="LoadMetadata"/>
249
    /// </summary>
250
    /// <returns></returns>
251
    public string GetDistinctLoggingTask()
252
    {
253
        var catalogueMetadatas = GetAllCatalogues().ToArray();
86✔
254

255
        if (!catalogueMetadatas.Any())
86!
256
            throw new Exception($"There are no Catalogues associated with load metadata (ID={ID})");
×
257

258
        var cataloguesWithoutLoggingTasks =
86✔
259
            catalogueMetadatas.Where(c => string.IsNullOrWhiteSpace(c.LoggingDataTask)).ToArray();
182✔
260

261
        if (cataloguesWithoutLoggingTasks.Any())
86✔
262
            throw new Exception(
2✔
263
                $"The following Catalogues do not have a LoggingDataTask specified:{cataloguesWithoutLoggingTasks.Aggregate("", (s, n) => $"{s}{n}(ID={n.ID}),")}");
4✔
264

265
        var distinctLoggingTasks = catalogueMetadatas.Select(c => c.LoggingDataTask).Distinct().ToArray();
176✔
266
        return distinctLoggingTasks.Length >= 2
84!
267
            ? throw new Exception(
84✔
268
                $"There are {distinctLoggingTasks.Length} logging tasks in Catalogues belonging to this metadata (ID={ID})")
84✔
269
            : distinctLoggingTasks[0];
84✔
270
    }
271

272
    /// <summary>
273
    /// Return all <see cref="TableInfo"/> underlying the <see cref="Catalogue"/>(s) which use this load (what tables will be loaded by the DLE).
274
    /// </summary>
275
    /// <param name="includeLookups">true to include lookup tables (e.g. z_sex etc) configured in the <see cref="Catalogue"/>(s)</param>
276
    /// <returns></returns>
277
    public List<TableInfo> GetDistinctTableInfoList(bool includeLookups)
278
    {
279
        var toReturn = new List<TableInfo>();
8✔
280

281
        foreach (var catalogueMetadata in GetAllCatalogues())
32✔
282
            foreach (TableInfo tableInfo in catalogueMetadata.GetTableInfoList(includeLookups))
32✔
283
                if (!toReturn.Contains(tableInfo))
8✔
284
                    toReturn.Add(tableInfo);
8✔
285

286
        return toReturn;
8✔
287
    }
288

289
    /// <inheritdoc/>
290
    public DiscoveredServer GetDistinctLiveDatabaseServer()
291
    {
292
        var normalTables = new HashSet<ITableInfo>();
226✔
293
        var lookupTables = new HashSet<ITableInfo>();
226✔
294

295
        foreach (var catalogue in GetAllCatalogues())
928✔
296
        {
297
            catalogue.GetTableInfos(out var normal, out var lookup);
238✔
298

299
            foreach (var n in normal)
952✔
300
                normalTables.Add(n);
238✔
301
            foreach (var l in lookup)
476!
302
                lookupTables.Add(l);
×
303
        }
304

305
        if (normalTables.Any())
226!
306
            return DataAccessPortal.ExpectDistinctServer(normalTables.ToArray(), DataAccessContext.DataLoad, true);
226✔
307

308
        return lookupTables.Any()
×
309
            ? DataAccessPortal.ExpectDistinctServer(lookupTables.ToArray(), DataAccessContext.DataLoad, true)
×
310
            : throw new Exception(
×
311
                $"LoadMetadata {this} has no TableInfos configured (or possibly the tables have been deleted resulting in MISSING ColumnInfos?)");
×
312
    }
313

314
    /// <inheritdoc/>
315
    public IHasDependencies[] GetObjectsThisDependsOn() => null;
×
316

317
    /// <inheritdoc/>
318
    public IHasDependencies[] GetObjectsDependingOnThis() => GetAllCatalogues().ToArray();
74✔
319

320
    /// <summary>
321
    /// Tests that the logging database for the load is reachable and that it has an appropriate logging task for the load (if not a new task will be created 'Loading X')
322
    /// </summary>
323
    /// <param name="catalogue"></param>
324
    public void EnsureLoggingWorksFor(ICatalogue catalogue)
325
    {
326
        //if there's no logging task / logging server set them up with the same name as the lmd
327
        IExternalDatabaseServer loggingServer;
328

329
        if (catalogue.LiveLoggingServer_ID == null)
14!
330
        {
331
            loggingServer = CatalogueRepository.GetDefaultFor(PermissableDefaults.LiveLoggingServer_ID);
×
332

333
            if (loggingServer != null)
×
334
                catalogue.LiveLoggingServer_ID = loggingServer.ID;
×
335
            else
336
                throw new NotSupportedException(
×
337
                    "You do not yet have any logging servers configured so cannot create data loads");
×
338
        }
339
        else
340
        {
341
            loggingServer = Repository.GetObjectByID<ExternalDatabaseServer>(catalogue.LiveLoggingServer_ID.Value);
14✔
342
        }
343

344
        //if there's no logging task yet and there's a logging server
345
        if (string.IsNullOrWhiteSpace(catalogue.LoggingDataTask))
14✔
346
        {
347
            var lm = new LogManager(loggingServer);
14✔
348
            var loggingTaskName = Name;
14✔
349

350
            lm.CreateNewLoggingTaskIfNotExists(loggingTaskName);
14✔
351
            catalogue.LoggingDataTask = loggingTaskName;
14✔
352
            catalogue.SaveToDatabase();
14✔
353
        }
354
    }
14✔
355

356
    /// <inheritdoc/>
357
    public IQuerySyntaxHelper GetQuerySyntaxHelper()
358
    {
359
        var syntax = GetAllCatalogues().Select(c => c.GetQuerySyntaxHelper()).Distinct().ToArray();
4✔
360
        return syntax.Length > 1
2!
361
            ? throw new Exception(
2✔
362
                $"LoadMetadata '{this}' has multiple underlying Catalogue Live Database Type(s) - not allowed")
2✔
363
            : syntax.SingleOrDefault();
2✔
364
    }
365

366
    /// <summary>
367
    /// Returns all runs since each LoadMetadata has its own task and all runs apply to that task and hence this object
368
    /// </summary>
369
    /// <param name="runs"></param>
370
    /// <returns></returns>
371
    public IEnumerable<ArchivalDataLoadInfo> FilterRuns(IEnumerable<ArchivalDataLoadInfo> runs) => runs;
12✔
372

373
    public static bool UsesPersistentRaw(ILoadMetadata loadMetadata)
374
    {
375
        return loadMetadata.CatalogueRepository.GetExtendedProperties(ExtendedProperty.PersistentRaw,
88✔
376
            loadMetadata).Any(p => p.Value == "true");
88✔
377
    }
378
}
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