Coverage Report

Created: 2025-07-11 06:33

/src/bag/api/bag_valuetable.cpp
Line
Count
Source (jump to first uncovered line)
1
2
#include "bag_compounddatatype.h"
3
#include "bag_georefmetadatalayerdescriptor.h"
4
#include "bag_dataset.h"
5
#include "bag_exceptions.h"
6
#include "bag_hdfhelper.h"
7
#include "bag_private.h"
8
#include "bag_valuetable.h"
9
10
#include <algorithm>
11
#include <array>
12
#include <H5Cpp.h>
13
14
15
namespace BAG {
16
17
namespace {
18
19
//! Convert a chunk of memory and a definition into a Record.
20
/*!
21
\param buffer
22
    A chunk of memory representing a Record for a georeferenced metadata layer.
23
\param definition
24
    The definition of the Record for a georeferenced metadata layer.
25
26
\return
27
    A georeferenced metadata layer record using the buffer and definition.
28
*/
29
Record convertMemoryToRecord(
30
    const uint8_t* const buffer,
31
    const RecordDefinition& definition)
32
99
{
33
99
    if (!buffer)
34
0
        return {};
35
36
99
    Record record;
37
99
    record.reserve(definition.size());
38
39
99
    size_t fieldOffset = 0;
40
41
99
    for (const auto& field : definition)
42
1.44k
    {
43
1.44k
        const auto fieldType = static_cast<DataType>(field.type);
44
1.44k
        CompoundDataType value;
45
1.44k
        const char* str = nullptr;
46
47
1.44k
        switch(fieldType)
48
1.44k
        {
49
387
        case DT_BOOLEAN:
50
387
            value = *reinterpret_cast<const bool*>(buffer + fieldOffset);
51
387
            break;
52
393
        case DT_FLOAT32:
53
393
            value = *reinterpret_cast<const float*>(buffer + fieldOffset);
54
393
            break;
55
96
        case DT_UINT32:
56
96
            value = *reinterpret_cast<const uint32_t*>(buffer + fieldOffset);
57
96
            break;
58
567
        case DT_STRING:
59
567
        {
60
567
            const auto address =
61
567
                *reinterpret_cast<const std::uintptr_t*>(buffer + fieldOffset);
62
567
            str = reinterpret_cast<const char*>(address);
63
567
            value = std::string{str ? str : ""};
64
65
            // Clean up the char* allocated by HDF reading.
66
567
            free(const_cast<char*>(str));
67
567
            break;
68
0
        }
69
0
        case DT_UINT8:  //[[fallthrough]]
70
0
        case DT_UINT16:  //[[fallthrough]]
71
0
        case DT_UINT64:  //[[fallthrough]]
72
0
        case DT_COMPOUND:  //[[fallthrough]]
73
0
        case DT_UNKNOWN_DATA_TYPE:  //[[fallthrough]]
74
0
        default:
75
0
            throw UnsupportedDataType{};
76
1.44k
        }
77
78
1.44k
        record.emplace_back(value);
79
1.44k
        fieldOffset += Layer::getElementSize(fieldType);
80
1.44k
    }
81
82
99
    return record;
83
99
}
84
85
//! Convert a Record into a chunk of memory.
86
/*!
87
\param record
88
    The record to convert into a chunk of memory.
89
\param buffer
90
    The memory to store the record in.
91
*/
92
void convertRecordToMemory(
93
    const Record& record,
94
    uint8_t* buffer)
95
0
{
96
0
    size_t fieldOffset = 0;  // offset into buffer to the field
97
98
0
    for (const auto& field : record)
99
0
    {
100
0
        const auto fieldType = field.getType();
101
0
        switch(fieldType)
102
0
        {
103
0
        case DT_BOOLEAN:
104
0
            *reinterpret_cast<bool*>(buffer + fieldOffset) = field.asBool();
105
0
            break;
106
0
        case DT_FLOAT32:
107
0
            *reinterpret_cast<float*>(buffer + fieldOffset) = field.asFloat();
108
0
            break;
109
0
        case DT_UINT32:
110
0
            *reinterpret_cast<uint32_t*>(buffer + fieldOffset) = field.asUInt32();
111
0
            break;
112
0
        case DT_STRING:
113
0
        {
114
0
            *reinterpret_cast<char**>(buffer + fieldOffset) =
115
0
                const_cast<char*>(field.asString().data());
116
0
            break;
117
0
        }
118
0
        case DT_UINT8:  //[[fallthrough]]
119
0
        case DT_UINT16:  //[[fallthrough]]
120
0
        case DT_UINT64:  //[[fallthrough]]
121
0
        case DT_COMPOUND:  //[[fallthrough]]
122
0
        case DT_UNKNOWN_DATA_TYPE:  //[[fallthrough]]
123
0
        default:
124
0
            throw UnsupportedDataType{};
125
0
        }
126
127
0
        fieldOffset += Layer::getElementSize(fieldType);
128
0
    }
129
0
}
130
131
}  // namespace
132
133
//! Constructor.
134
/*!
135
\param layer
136
    The layer the value table holds records/values for.
137
*/
138
ValueTable::ValueTable(
139
    const GeorefMetadataLayer& layer)
140
55
    : m_layer(layer)
141
55
{
142
    // Read the Records DataSet.
143
55
    const auto& h5valueDataSet = m_layer.getValueDataSet();
144
145
55
    const auto fileDataSpace = h5valueDataSet.getSpace();
146
55
    hsize_t numRecords = 0;
147
55
    const auto numDims = fileDataSpace.getSimpleExtentDims(&numRecords);
148
55
    if (numDims != 1)
149
0
        throw InvalidValueSize{};
150
151
55
    m_records.resize(numRecords);
152
153
55
    if (numRecords == 1)  // No user defined records.
154
1
        return;
155
156
54
    constexpr hsize_t startIndex = 0;
157
54
    fileDataSpace.selectHyperslab(H5S_SELECT_SET, &numRecords, &startIndex);
158
159
54
    auto pDescriptor = std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
160
54
        m_layer.getDescriptor());
161
54
    const auto& definition = pDescriptor->getDefinition();
162
163
54
    const size_t recordSize = getRecordSize(definition);
164
54
    std::vector<uint8_t> buffer(recordSize * numRecords, 0);
165
166
54
    const auto memDataType = createH5memoryCompType(definition);
167
54
    const ::H5::DataSpace memDataSpace;
168
54
    memDataSpace.setExtentSimple(1, &numRecords);
169
170
54
    h5valueDataSet.read(buffer.data(), memDataType, memDataSpace, fileDataSpace);
171
172
    // Convert the raw memory into Records.
173
54
    size_t rawIndex = 0;
174
175
54
    for (auto& record : m_records)
176
99
    {
177
99
        record = convertMemoryToRecord(buffer.data() + (rawIndex * recordSize),
178
99
            definition);
179
99
        ++rawIndex;
180
99
    }
181
54
}
182
183
//! Add a record/value to the end of the list.
184
/*!
185
\param record
186
    The record/value.
187
188
\return
189
    The index of the added record.
190
*/
191
size_t ValueTable::addRecord(
192
    const Record& record)
193
0
{
194
0
    if (!this->validateRecord(record))
195
0
        throw InvalidValue{};
196
197
0
    const auto newKey = m_records.size();
198
0
    this->writeRecord(newKey, record);
199
200
0
    m_records.emplace_back(record);
201
202
0
    return newKey;
203
0
}
204
205
//! Add multiple records/values to the end of the list.
206
/*!
207
\param records
208
    The records/values.
209
*/
210
void ValueTable::addRecords(
211
    const Records& records)
212
0
{
213
0
    if (records.empty())
214
0
        return;
215
216
0
    const bool allValid = std::all_of(cbegin(records), cend(records),
217
0
        [this](const auto& record) {
218
0
            return this->validateRecord(record);
219
0
        });
220
0
    if (!allValid)
221
0
        throw InvalidValue{};
222
223
0
    this->writeRecords(records);
224
225
0
    m_records.insert(end(m_records), cbegin(records), cend(records));
226
0
}
227
228
//! Convert a record/value to a chunk of memory.
229
/*!
230
\param record
231
    The record/value to convert.
232
233
\return
234
    A copy of the record/value as a chunk of memory.
235
*/
236
std::vector<uint8_t> ValueTable::convertRecordToRaw(
237
    const Record& record) const
238
0
{
239
0
    auto pDescriptor =
240
0
        std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
241
0
            m_layer.getDescriptor());
242
243
0
    std::vector<uint8_t> buffer(getRecordSize(pDescriptor->getDefinition()), 0);
244
245
0
    convertRecordToMemory(record, buffer.data());
246
247
0
    return buffer;
248
0
}
249
250
//! Convert multiple records/values to a chunk of memory.
251
/*!
252
\param records
253
    The records/values to convert.
254
255
\return
256
    A copy of the records/values as a chunk of memory.
257
*/
258
std::vector<uint8_t> ValueTable::convertRecordsToRaw(
259
    const std::vector<Record>& records) const
260
0
{
261
0
    if (records.empty())
262
0
        return {};
263
264
0
    auto pDescriptor =
265
0
        std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
266
0
            m_layer.getDescriptor());
267
268
0
    const auto recordSize = getRecordSize(pDescriptor->getDefinition());
269
0
    std::vector<uint8_t> buffer(recordSize * records.size(), 0);
270
271
    // Write values into memory.
272
0
    size_t offset = 0;
273
274
0
    for (const auto& record : records)
275
0
    {
276
0
        convertRecordToMemory(record, buffer.data() + offset);
277
0
        offset += recordSize;
278
0
    }
279
280
0
    return buffer;
281
0
}
282
283
//! Retrieve the record/value definition.
284
/*!
285
\return
286
    The list of fields that define the record/value.
287
*/
288
const RecordDefinition& ValueTable::getDefinition() const & noexcept
289
0
{
290
0
    return std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
291
0
        m_layer.getDescriptor())->getDefinition();
292
0
}
293
294
//! Retrieve the value of a specific field in a specific record.
295
/*!
296
\param key
297
    The key of the record/value.
298
    Must be greater than 0.
299
\param name
300
    The name of the field.
301
302
\return
303
    The value specified.
304
    An exception is thrown if the key or field name is invalid.
305
*/
306
const CompoundDataType& ValueTable::getValue(
307
    size_t key,
308
    const std::string& name) const &
309
0
{
310
0
    if (key == 0 || key >= m_records.size())
311
0
        throw ValueNotFound{};
312
313
0
    const size_t fieldIndex = this->getFieldIndex(name);
314
315
0
    return this->getValue(key, fieldIndex);
316
0
}
317
318
//! Retrieve the value of a specific field in a specific record/value.
319
/*!
320
\param key
321
    The key of the record/value.
322
    Must be greater than 0.
323
\param fieldIndex
324
    The index of the field.
325
326
\return
327
    The value specified.
328
    An exception is thrown if the record index or field index are invalid.
329
*/
330
const CompoundDataType& ValueTable::getValue(
331
    size_t key,
332
    size_t fieldIndex) const &
333
0
{
334
0
    if (key == 0 || key >= m_records.size())
335
0
        throw ValueNotFound{};
336
337
0
    const auto& definition = this->getDefinition();
338
0
    if (fieldIndex >= definition.size())
339
0
        throw FieldNotFound{};
340
341
0
    const auto& record = m_records[key];
342
343
0
    return record[fieldIndex];
344
0
}
345
346
//! Retrieve the field index of the named field.
347
/*!
348
\param name
349
    The name of the field.
350
351
\return
352
    The field index of the named field.
353
*/
354
size_t ValueTable::getFieldIndex(
355
    const std::string& name) const
356
0
{
357
0
    const auto& definition = this->getDefinition();
358
0
    size_t fieldIndex = 0;
359
360
0
    for (const auto& field : definition)
361
0
    {
362
0
        if (name == field.name)
363
0
            return fieldIndex;
364
365
0
        ++fieldIndex;
366
0
    }
367
368
0
    throw FieldNotFound{};
369
0
}
370
371
//! Retrieve the field name of the indexed field.
372
/*!
373
\param index
374
    The index of the field.
375
376
\return
377
    The field name of the indexed field.
378
*/
379
const char* ValueTable::getFieldName(
380
    size_t index) const &
381
0
{
382
0
    const auto& definition = this->getDefinition();
383
0
    if (index >= definition.size())
384
0
        throw FieldNotFound{};
385
386
0
    return definition[index].name;
387
0
}
388
389
//! Retrieve all the records/values.
390
/*!
391
\return
392
    All the records/values.
393
    NOTE!  This includes the no data value record at index 0.
394
*/
395
const Records& ValueTable::getRecords() const & noexcept
396
0
{
397
0
    return m_records;
398
0
}
399
400
//! Set a value in a specific field in a specific record.
401
/*!
402
\param key
403
    The record/value key.
404
    Must be greater than 0.
405
\param name
406
    The name of the field.
407
\param value
408
    The value to put into the field.
409
*/
410
void ValueTable::setValue(
411
    size_t key,
412
    const std::string& name,
413
    const CompoundDataType& value)
414
0
{
415
0
    if (key == 0 || key >= m_records.size())
416
0
        throw ValueNotFound{};
417
418
0
    const size_t fieldIndex = this->getFieldIndex(name);
419
420
0
    this->setValue(key, fieldIndex, value);
421
0
}
422
423
//! Set a value in a specific field in a specific record.
424
/*!
425
\param key
426
    The record key.
427
    Must be greater than 0.
428
\param fieldIndex
429
    The index of the field.
430
\param value
431
    The value to put into the field.
432
*/
433
void ValueTable::setValue(
434
    size_t key,
435
    size_t fieldIndex,
436
    const CompoundDataType& value)
437
0
{
438
0
    if (key == 0 || key >= m_records.size())
439
0
        throw ValueNotFound{};
440
441
0
    auto& record = m_records[key];
442
0
    record[fieldIndex] = value;
443
444
0
    this->writeRecord(key, record);
445
0
}
446
447
//! Determine if the specified record/value matches the definition used by the value table.
448
/*!
449
\param record
450
    The record/value.
451
452
\return
453
    \e true if the record/value matches the definition.
454
    \e false otherwise
455
*/
456
bool ValueTable::validateRecord(
457
    const Record& record) const
458
0
{
459
0
    auto pDescriptor =
460
0
        std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
461
0
            m_layer.getDescriptor());
462
0
    if (!pDescriptor)
463
0
        throw InvalidDescriptor{};
464
465
0
    const auto& definition = pDescriptor->getDefinition();
466
467
0
    if (record.size() != definition.size())
468
0
        return false;
469
470
0
    size_t defIndex = 0;
471
472
0
    for (const auto& field : record)
473
0
    {
474
0
        const auto defType = static_cast<DataType>(definition[defIndex++].type);
475
476
0
        if (defType == DT_UNKNOWN_DATA_TYPE
477
0
            || field.getType() == DT_UNKNOWN_DATA_TYPE)
478
0
            return false;
479
480
0
        const auto fieldType = field.getType();
481
0
        if (fieldType != defType)
482
0
            return false;
483
0
    }
484
485
0
    return true;
486
0
}
487
488
//! Write a record/value to the HDF5 DataSet at the specified key.
489
/*!
490
\param key
491
    The record key.
492
    Must be greater than 0.
493
\param record
494
    The record/value to write.
495
*/
496
void ValueTable::writeRecord(
497
    size_t key,
498
    const Record& record)
499
0
{
500
0
    if (key == 0 || key > m_records.size())
501
0
        throw InvalidValueKey{};
502
503
0
    const hsize_t fileRecordIndex = key;
504
505
    // Prepare the memory details.
506
0
    const auto rawMemory = this->convertRecordToRaw(record);
507
508
0
    auto pDescriptor =
509
0
        std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
510
0
            m_layer.getDescriptor());
511
512
0
    const auto memDataType = createH5memoryCompType(pDescriptor->getDefinition());
513
514
0
    constexpr hsize_t one = 1;
515
0
    ::H5::DataSpace memDataSpace(1, &one, &one);
516
517
    // Prepare the file details.
518
0
    const auto& h5valueDataSet = m_layer.getValueDataSet();
519
520
0
    if (key == m_records.size())
521
0
    {
522
        // Make room for a new record.
523
0
        const hsize_t newNumRecords = fileRecordIndex + 1;
524
0
        h5valueDataSet.extend(&newNumRecords);
525
0
    }
526
527
0
    const auto fileDataSpace = h5valueDataSet.getSpace();
528
529
    // select the record to write
530
0
    fileDataSpace.selectElements(H5S_SELECT_SET, 1, &fileRecordIndex);
531
532
0
    h5valueDataSet.write(rawMemory.data(), memDataType, memDataSpace,
533
0
        fileDataSpace);
534
0
}
535
536
//! Write multiple records/values at the end of the list to the HDF5 DataSet.
537
/*!
538
\param records
539
    The records/values.
540
*/
541
void ValueTable::writeRecords(
542
    const std::vector<Record>& records)
543
0
{
544
0
    if (records.empty())
545
0
        return;
546
547
    // Prepare the memory details.
548
0
    const auto rawMemory = this->convertRecordsToRaw(records);
549
550
0
    auto pDescriptor =
551
0
        std::dynamic_pointer_cast<const GeorefMetadataLayerDescriptor>(
552
0
            m_layer.getDescriptor());
553
554
0
    const auto memDataType = createH5memoryCompType(pDescriptor->getDefinition());
555
556
0
    const hsize_t numRecords = records.size();
557
0
    ::H5::DataSpace memDataSpace(1, &numRecords, &numRecords);
558
559
    // Prepare the file details.
560
0
    const auto& h5valueDataSet = m_layer.getValueDataSet();
561
562
    // Make room for the new records.
563
0
    const hsize_t newNumRecords = m_records.size() + numRecords;
564
0
    h5valueDataSet.extend(&newNumRecords);
565
566
0
    const auto fileDataSpace = h5valueDataSet.getSpace();
567
568
    // Specify the key to begin writing to.
569
0
    const hsize_t keyToModify = m_records.size();
570
0
    fileDataSpace.selectHyperslab(H5S_SELECT_SET, &numRecords, &keyToModify);
571
572
0
    h5valueDataSet.write(rawMemory.data(), memDataType, memDataSpace,
573
0
        fileDataSpace);
574
0
}
575
576
}  // namespace BAG
577