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

paulmthompson / WhiskerToolbox / 17270491352

27 Aug 2025 02:57PM UTC coverage: 65.333%. Remained the same
17270491352

push

github

paulmthompson
Merge branch 'main' of https://github.com/paulmthompson/WhiskerToolbox

352 of 628 new or added lines in 92 files covered. (56.05%)

357 existing lines in 24 files now uncovered.

26429 of 40453 relevant lines covered (65.33%)

1119.34 hits per line

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

39.57
/src/DataManager/utils/TableView/core/TableView.cpp
1
#include "TableView.h"
2

3
#include "utils/TableView/adapters/DataManagerExtension.h"
4
#include "utils/TableView/columns/IColumn.h"
5
#include "utils/TableView/interfaces/ILineSource.h"
6
#include "utils/TableView/interfaces/IRowSelector.h"
7

8

9
#include <algorithm>
10
#include <set>
11
#include <stdexcept>
12

13
TableView::TableView(std::unique_ptr<IRowSelector> rowSelector,
72✔
14
                     std::shared_ptr<DataManagerExtension> dataManager)
72✔
15
    : m_rowSelector(std::move(rowSelector)),
72✔
16
      m_dataManager(std::move(dataManager)) {
72✔
17
    if (!m_rowSelector) {
72✔
18
        throw std::invalid_argument("IRowSelector cannot be null");
×
19
    }
20
    if (!m_dataManager) {
72✔
21
        throw std::invalid_argument("DataManagerExtension cannot be null");
×
22
    }
23
}
72✔
24

25
TableView::TableView(TableView && other) noexcept
42✔
26
    : m_rowSelector(std::move(other.m_rowSelector)),
42✔
27
      m_dataManager(std::move(other.m_dataManager)),
42✔
28
      m_columns(std::move(other.m_columns)),
42✔
29
      m_colNameToIndex(std::move(other.m_colNameToIndex)),
42✔
30
      m_planCache(std::move(other.m_planCache)) {}
42✔
31

32
TableView & TableView::operator=(TableView && other) {
×
33
    if (this != &other) {
×
34
        m_rowSelector = std::move(other.m_rowSelector);
×
35
        m_dataManager = std::move(other.m_dataManager);
×
36
        m_columns = std::move(other.m_columns);
×
37
        m_colNameToIndex = std::move(other.m_colNameToIndex);
×
38
        m_planCache = std::move(other.m_planCache);
×
39
    }
40
    return *this;
×
41
}
42

43
size_t TableView::getRowCount() const {
58✔
44
    // Prefer expanded row count if any execution plan has entity-expanded rows cached
45
    for (auto const & entry : m_planCache) {
58✔
46
        if (!entry.second.getRows().empty()) {
1✔
47
            return entry.second.getRows().size();
1✔
48
        }
49
    }
50
    // If nothing cached yet, proactively attempt expansion using a line-source dependent column
51
    // Find a column whose source is an ILineSource
52
    for (auto const & column : m_columns) {
163✔
53
        try {
54
            auto const & dep = column->getSourceDependency();
119✔
55
            // Query line source without mutating the cache in a surprising way
56
            auto lineSource = m_dataManager->getLineSource(dep);
119✔
57
            if (lineSource) {
119✔
58
                // Trigger plan generation for this source to populate expansion rows
59
                auto & self = const_cast<TableView &>(*this);
13✔
60
                ExecutionPlan const & plan = self.getExecutionPlanFor(dep);
13✔
61
                if (!plan.getRows().empty()) {
13✔
62
                    return plan.getRows().size();
12✔
63
                }
64
                break; // only expand based on the first line source column
1✔
65
            }
66
        } catch (...) {
132✔
67
            // Ignore and continue
68
        }
×
69
    }
70
    return m_rowSelector->getRowCount();
45✔
71
}
72

73
size_t TableView::getColumnCount() const {
47✔
74
    return m_columns.size();
47✔
75
}
76

77
std::vector<std::string> TableView::getColumnNames() const {
39✔
78
    std::vector<std::string> names;
39✔
79
    names.reserve(m_columns.size());
39✔
80

81
    for (auto const & column: m_columns) {
202✔
82
        names.push_back(column->getName());
163✔
83
    }
84

85
    return names;
39✔
86
}
×
87

88
bool TableView::hasColumn(std::string const & name) const {
398✔
89
    return m_colNameToIndex.find(name) != m_colNameToIndex.end();
398✔
90
}
91

92
std::type_info const & TableView::getColumnType(std::string const & name) const {
×
93
    auto it = m_colNameToIndex.find(name);
×
94
    if (it == m_colNameToIndex.end()) {
×
95
        throw std::runtime_error("Column '" + name + "' not found in table");
×
96
    }
97
    
98
    return m_columns[it->second]->getType();
×
99
}
100

101
std::type_index TableView::getColumnTypeIndex(std::string const & name) const {
×
102
    return std::type_index(getColumnType(name));
×
103
}
104

105
ColumnDataVariant TableView::getColumnDataVariant(std::string const & name) {
×
106
    auto type_index = getColumnTypeIndex(name);
×
107
    
108
    // Dispatch from element type_index to vector type using template metaprogramming
109
    std::optional<ColumnDataVariant> result;
×
110
    
111
    for_each_type<SupportedColumnElementTypes>([&](auto, auto type_instance) {
×
112
        using ElementType = std::decay_t<decltype(type_instance)>;
113
        
114
        if (!result.has_value() && type_index == std::type_index(typeid(ElementType))) {
×
115
            // Found matching element type, get the vector data
116
            auto vectorData = getColumnValues<ElementType>(name);
×
117
            result = vectorData;  // This will construct the variant with the correct vector type
×
118
        }
×
119
    });
×
120

121
    if (!result.has_value()) {
×
122
        throw std::runtime_error("Unsupported column type: " + std::string(type_index.name()) + 
×
123
                                " for column: " + name);
×
124
    }
125
    
126
    return result.value();
×
127
}
×
128

129

130
void TableView::materializeAll() {
×
131
    std::set<std::string> materializing;
×
132

133
    for (auto const & column: m_columns) {
×
134
        if (!column->isMaterialized()) {
×
135
            materializeColumn(column->getName(), materializing);
×
136
        }
137
    }
138
}
×
139

140
void TableView::clearCache() {
×
141
    // Clear column caches
142
    for (auto & column: m_columns) {
×
143
        column->clearCache();
×
144
    }
145

146
    // Clear execution plan cache
147
    m_planCache.clear();
×
148
}
×
149

150
ExecutionPlan const & TableView::getExecutionPlanFor(std::string const & sourceName) {
165✔
151
    // Check cache first
152
    auto it = m_planCache.find(sourceName);
165✔
153
    if (it != m_planCache.end()) {
165✔
154
        return it->second;
77✔
155
    }
156

157
    // Generate new plan
158
    ExecutionPlan plan = generateExecutionPlan(sourceName);
88✔
159

160
    // Store in cache and return reference
161
    auto [insertedIt, inserted] = m_planCache.emplace(sourceName, std::move(plan));
88✔
162
    if (!inserted) {
88✔
163
        throw std::runtime_error("Failed to cache ExecutionPlan for source: " + sourceName);
×
164
    }
165

166
    return insertedIt->second;
88✔
167
}
88✔
168

169
void TableView::addColumn(std::shared_ptr<IColumn> column) {
255✔
170
    if (!column) {
255✔
171
        throw std::invalid_argument("Column cannot be null");
×
172
    }
173

174
    std::string const & name = column->getName();
255✔
175

176
    // Check for duplicate names
177
    if (hasColumn(name)) {
255✔
178
        throw std::runtime_error("Column '" + name + "' already exists");
×
179
    }
180

181
    // Add to collections
182
    size_t index = m_columns.size();
255✔
183
    m_columns.push_back(std::move(column));
255✔
184
    m_colNameToIndex[name] = index;
255✔
185
}
255✔
186

187
void TableView::materializeColumn(std::string const & columnName, std::set<std::string> & materializing) {
×
188
    // Check for circular dependencies
189
    if (materializing.find(columnName) != materializing.end()) {
×
190
        throw std::runtime_error("Circular dependency detected involving column: " + columnName);
×
191
    }
192

193
    // Check if column exists
194
    if (!hasColumn(columnName)) {
×
195
        throw std::runtime_error("Column '" + columnName + "' not found");
×
196
    }
197

198
    auto & column = m_columns[m_colNameToIndex[columnName]];
×
199

200
    // If already materialized, nothing to do
201
    if (column->isMaterialized()) {
×
202
        return;
×
203
    }
204

205
    // Mark as being materialized
206
    materializing.insert(columnName);
×
207

208
    // Materialize dependencies first
209
    auto const dependencies = column->getDependencies();
×
210
    for (auto const & dependency: dependencies) {
×
211
        if (hasColumn(dependency)) {
×
212
            materializeColumn(dependency, materializing);
×
213
        }
214
    }
215

216
    // Materialize this column
217
    column->materialize(this);// Use the IColumn interface method
×
218

219
    // Remove from materializing set
220
    materializing.erase(columnName);
×
221
}
×
222

223
ExecutionPlan TableView::generateExecutionPlan(std::string const & sourceName) {
88✔
224
    // Try to get the data source to understand its structure
225
    // First try as analog source
226

227
    
228

229
    auto analogSource = m_dataManager->getAnalogSource(sourceName);
88✔
230
    if (analogSource) {
88✔
231
        // Generate plan based on row selector type for analog data
232
        if (auto intervalSelector = dynamic_cast<IntervalSelector *>(m_rowSelector.get())) {
40✔
233
            auto const & intervals = intervalSelector->getIntervals();
24✔
234
            auto timeFrame = intervalSelector->getTimeFrame();
24✔
235
            return ExecutionPlan(intervals, timeFrame);
24✔
236
        }
24✔
237

238
        if (auto timestampSelector = dynamic_cast<TimestampSelector *>(m_rowSelector.get())) {
16✔
239
            auto const & indices = timestampSelector->getTimestamps();
16✔
240
            auto timeFrame = timestampSelector->getTimeFrame();
16✔
241
            return ExecutionPlan(indices, timeFrame);
16✔
242
        }
16✔
243

244
        if (auto indexSelector = dynamic_cast<IndexSelector *>(m_rowSelector.get())) {
×
245
            auto const & indices = indexSelector->getIndices();
×
246
            std::vector<TimeFrameIndex> timeFrameIndices;
×
247
            timeFrameIndices.reserve(indices.size());
×
248

249
            for (size_t index: indices) {
×
250
                timeFrameIndices.emplace_back(static_cast<int64_t>(index));
×
251
            }
252

253
            std::cout << "WARNING: IndexSelector is not supported for analog data" << std::endl;
×
254
            return ExecutionPlan(std::move(timeFrameIndices), nullptr);
×
255
        }
×
256
    }
257

258
    // Try as interval source
259
    auto intervalSource = m_dataManager->getIntervalSource(sourceName);
48✔
260
    if (intervalSource) {
48✔
261
        // Generate plan based on row selector type for interval data
262
        if (auto intervalSelector = dynamic_cast<IntervalSelector *>(m_rowSelector.get())) {
23✔
263
            auto const & intervals = intervalSelector->getIntervals();
15✔
264
            auto timeFrame = intervalSelector->getTimeFrame();
15✔
265
            return ExecutionPlan(intervals, timeFrame);
15✔
266
        }
15✔
267

268
        if (auto timestampSelector = dynamic_cast<TimestampSelector *>(m_rowSelector.get())) {
8✔
269
            auto const & indices = timestampSelector->getTimestamps();
8✔
270
            auto timeFrame = timestampSelector->getTimeFrame();
8✔
271
            return ExecutionPlan(indices, timeFrame);
8✔
272
        }
8✔
273

274
        if (auto indexSelector = dynamic_cast<IndexSelector *>(m_rowSelector.get())) {
×
275
            auto const & indices = indexSelector->getIndices();
×
276
            std::vector<TimeFrameIndex> timeFrameIndices;
×
277
            timeFrameIndices.reserve(indices.size());
×
278

279
            for (size_t index: indices) {
×
280
                timeFrameIndices.emplace_back(static_cast<int64_t>(index));
×
281
            }
282

283
            std::cout << "WARNING: IndexSelector is not supported for interval data" << std::endl;
×
284
            return ExecutionPlan(std::move(timeFrameIndices), nullptr);
×
285
        }
×
286
    }
287

288
    // Try as event source
289
    auto eventSource = m_dataManager->getEventSource(sourceName);
25✔
290
    if (eventSource) {
25✔
291
        // Generate plan based on row selector type for event data
292
        if (auto intervalSelector = dynamic_cast<IntervalSelector *>(m_rowSelector.get())) {
9✔
293
            auto const & intervals = intervalSelector->getIntervals();
9✔
294
            auto timeFrame = intervalSelector->getTimeFrame();
9✔
295
            return ExecutionPlan(intervals, timeFrame);
9✔
296
        }
9✔
297

298
        if (auto timestampSelector = dynamic_cast<TimestampSelector *>(m_rowSelector.get())) {
×
299
            auto const & indices = timestampSelector->getTimestamps();
×
300
            auto timeFrame = timestampSelector->getTimeFrame();
×
301
            return ExecutionPlan(indices, timeFrame);
×
302
        }
×
303

304
        if (auto indexSelector = dynamic_cast<IndexSelector *>(m_rowSelector.get())) {
×
305
            auto const & indices = indexSelector->getIndices();
×
306
            std::vector<TimeFrameIndex> timeFrameIndices;
×
307
            timeFrameIndices.reserve(indices.size());
×
308

309
            for (size_t index: indices) {
×
310
                timeFrameIndices.emplace_back(static_cast<int64_t>(index));
×
311
            }
312

313
            std::cout << "WARNING: IndexSelector is not supported for event data" << std::endl;
×
314

315
            return ExecutionPlan(std::move(timeFrameIndices), nullptr);
×
316
        }
×
317
    }
318

319
    // Try as line source
320
    auto lineSource = m_dataManager->getLineSource(sourceName);
16✔
321
    if (lineSource) {
16✔
322
        // Default-on entity expansion for TimestampSelector
323
        if (auto timestampSelector = dynamic_cast<TimestampSelector *>(m_rowSelector.get())) {
14✔
324
            auto const & timestamps = timestampSelector->getTimestamps();
14✔
325
            auto timeFrame = timestampSelector->getTimeFrame();
14✔
326

327
            ExecutionPlan plan(std::vector<TimeFrameIndex>{}, timeFrame);
14✔
328
            // Build expanded rows: one row per line at that timestamp; drop timestamps with zero lines
329
            std::vector<RowId> rows;
14✔
330
            rows.reserve(timestamps.size());
14✔
331
            std::map<TimeFrameIndex, std::pair<size_t,size_t>> spans;
14✔
332

333
            // Determine if table contains any non-line columns; if so, we include singleton rows
334
            bool anyNonLineColumn = false;
14✔
335
            for (auto const & col : m_columns) {
116✔
336
                try {
337
                    auto const & dep = col->getSourceDependency();
103✔
338
                    if (!m_dataManager->getLineSource(dep)) {
103✔
339
                        anyNonLineColumn = true;
1✔
340
                        break;
1✔
341
                    }
342
                } catch (...) {
103✔
343
                    // Ignore
344
                }
×
345
            }
346

347
            size_t cursor = 0;
14✔
348
            for (auto const & t : timestamps) {
60✔
349
                auto const count = lineSource->getEntityCountAt(t);
46✔
350
                if (count == 0) {
46✔
351
                    if (anyNonLineColumn) {
8✔
352
                        spans.emplace(t, std::make_pair(cursor, static_cast<size_t>(1)));
3✔
353
                        rows.push_back(RowId{t, std::nullopt});
3✔
354
                        ++cursor;
3✔
355
                    }
356
                } else {
357
                    spans.emplace(t, std::make_pair(cursor, static_cast<size_t>(count)));
38✔
358
                    for (size_t i = 0; i < count; ++i) {
96✔
359
                        rows.push_back(RowId{t, static_cast<int>(i)});
58✔
360
                        ++cursor;
58✔
361
                    }
362
                }
363
            }
364

365
            plan.setRows(std::move(rows));
14✔
366
            plan.setTimeToRowSpan(std::move(spans));
14✔
367
            plan.setSourceId(DataSourceNameInterner::instance().intern(lineSource->getName()));
14✔
368
            plan.setSourceKind(ExecutionPlan::DataSourceKind::Line);
14✔
369
            return plan;
14✔
370
        }
14✔
371

372
        // IntervalSelector: keep legacy behavior (no expansion) for now
373
        if (auto intervalSelector = dynamic_cast<IntervalSelector *>(m_rowSelector.get())) {
×
374
            auto const & intervals = intervalSelector->getIntervals();
×
375
            auto timeFrame = intervalSelector->getTimeFrame();
×
376
            return ExecutionPlan(intervals, timeFrame);
×
377
        }
×
378

379
        if (auto indexSelector = dynamic_cast<IndexSelector *>(m_rowSelector.get())) {
×
380
            auto const & indices = indexSelector->getIndices();
×
381
            std::vector<TimeFrameIndex> timeFrameIndices;
×
382
            timeFrameIndices.reserve(indices.size());
×
383
            for (size_t index: indices) {
×
384
                timeFrameIndices.emplace_back(static_cast<int64_t>(index));
×
385
            }
386
            std::cout << "WARNING: IndexSelector is not supported for line data" << std::endl;
×
387
            auto plan = ExecutionPlan(std::move(timeFrameIndices), nullptr);
×
388
            plan.setSourceId(DataSourceNameInterner::instance().intern(lineSource->getName()));
×
389
            plan.setSourceKind(ExecutionPlan::DataSourceKind::Line);
×
390
            return plan;
×
391
        }
×
392
    }
393

394
    // Generic fallback: generate plan solely based on row selector when the source is unknown
395
    if (auto intervalSelector = dynamic_cast<IntervalSelector *>(m_rowSelector.get())) {
2✔
396
        auto const & intervals = intervalSelector->getIntervals();
×
397
        auto timeFrame = intervalSelector->getTimeFrame();
×
398
        std::cout << "WARNING: Data source '" << sourceName
399
                  << "' not found. Generating plan from IntervalSelector only." << std::endl;
×
400
        return ExecutionPlan(intervals, timeFrame);
×
401
    }
×
402

403
    if (auto timestampSelector = dynamic_cast<TimestampSelector *>(m_rowSelector.get())) {
2✔
404
        auto const & indices = timestampSelector->getTimestamps();
2✔
405
        auto timeFrame = timestampSelector->getTimeFrame();
2✔
406
        std::cout << "WARNING: Data source '" << sourceName
407
                  << "' not found. Generating plan from TimestampSelector only." << std::endl;
2✔
408
        return ExecutionPlan(indices, timeFrame);
2✔
409
    }
2✔
410

411
    if (auto indexSelector = dynamic_cast<IndexSelector *>(m_rowSelector.get())) {
×
412
        auto const & indices = indexSelector->getIndices();
×
413
        std::vector<TimeFrameIndex> timeFrameIndices;
×
414
        timeFrameIndices.reserve(indices.size());
×
415
        for (size_t index: indices) {
×
416
            timeFrameIndices.emplace_back(static_cast<int64_t>(index));
×
417
        }
418
        std::cout << "WARNING: Data source '" << sourceName
419
                  << "' not found. Generating plan from IndexSelector only." << std::endl;
×
420
        return ExecutionPlan(std::move(timeFrameIndices), nullptr);
×
421
    }
×
422

423
    throw std::runtime_error("Data source '" + sourceName + "' not found as analog, interval, event, or line source");
×
424
}
88✔
425

426
RowDescriptor TableView::getRowDescriptor(size_t row_index) const {
×
427
    if (m_rowSelector) {
×
428
        return m_rowSelector->getDescriptor(row_index);
×
429
    }
430
    return std::monostate{};
×
431
}
432

433
std::vector<EntityId> TableView::getRowEntityIds(size_t row_index) const {
×
434
    // Prefer entity-expanded plans if present
435
    for (auto const & [name, plan] : m_planCache) {
×
436
        (void)name;
437
        auto const & rows = plan.getRows();
×
438
        if (rows.empty() || row_index >= rows.size()) continue;
×
439
        auto const & row = rows[row_index];
×
440
        if (!row.entityIndex.has_value()) continue;
×
441

442
        // Resolve by source kind
443
        auto const sourceName = DataSourceNameInterner::instance().nameOf(plan.getSourceId());
×
444
        switch (plan.getSourceKind()) {
×
445
            case ExecutionPlan::DataSourceKind::Line: {
×
446
                auto lineSource = m_dataManager->getLineSource(sourceName);
×
447
                if (!lineSource) break;
×
448
                EntityId id = lineSource->getEntityIdAt(row.timeIndex, *row.entityIndex);
×
449
                if (id != 0) return {id};
×
450
                break;
×
451
            }
×
452
            case ExecutionPlan::DataSourceKind::Event: {
×
453
                auto eventSource = m_dataManager->getEventSource(sourceName);
×
454
                if (!eventSource) break;
×
455
                // For events we typically do not expand per-entity rows yet; fall through
456
                break;
×
457
            }
×
NEW
458
            case ExecutionPlan::DataSourceKind::IntervalKind: {
×
459
                auto intervalSource = m_dataManager->getIntervalSource(sourceName);
×
460
                if (!intervalSource) break;
×
461
                // Interval entity expansion not yet implemented here
462
                break;
×
463
            }
×
464
            default: break;
×
465
        }
466
    }
×
467
    return {};
×
468
}
469

470
std::unique_ptr<IRowSelector> TableView::cloneRowSelectorFiltered(std::vector<size_t> const & keep_indices) const {
×
471
    if (!m_rowSelector) {
×
472
        return nullptr;
×
473
    }
474

475
    // IndexSelector
476
    if (auto indexSelector = dynamic_cast<IndexSelector const *>(m_rowSelector.get())) {
×
477
        std::vector<size_t> const & indices = indexSelector->getIndices();
×
478
        std::vector<size_t> filtered;
×
479
        filtered.reserve(keep_indices.size());
×
480
        for (size_t k : keep_indices) {
×
481
            if (k < indices.size()) {
×
482
                filtered.push_back(indices[k]);
×
483
            }
484
        }
485
        return std::make_unique<IndexSelector>(std::move(filtered));
×
486
    }
×
487

488
    // TimestampSelector
489
    if (auto timestampSelector = dynamic_cast<TimestampSelector const *>(m_rowSelector.get())) {
×
490
        auto const & timestamps = timestampSelector->getTimestamps();
×
491
        auto timeFrame = timestampSelector->getTimeFrame();
×
492
        std::vector<TimeFrameIndex> filtered;
×
493
        filtered.reserve(keep_indices.size());
×
494
        for (size_t k : keep_indices) {
×
495
            if (k < timestamps.size()) {
×
496
                filtered.push_back(timestamps[k]);
×
497
            }
498
        }
499
        return std::make_unique<TimestampSelector>(std::move(filtered), timeFrame);
×
500
    }
×
501

502
    // IntervalSelector
503
    if (auto intervalSelector = dynamic_cast<IntervalSelector const *>(m_rowSelector.get())) {
×
504
        auto const & intervals = intervalSelector->getIntervals();
×
505
        auto timeFrame = intervalSelector->getTimeFrame();
×
506
        std::vector<TimeFrameInterval> filtered;
×
507
        filtered.reserve(keep_indices.size());
×
508
        for (size_t k : keep_indices) {
×
509
            if (k < intervals.size()) {
×
510
                filtered.push_back(intervals[k]);
×
511
            }
512
        }
513
        return std::make_unique<IntervalSelector>(std::move(filtered), timeFrame);
×
514
    }
×
515

516
    // Fallback: preserve by indices if unknown type
517
    std::vector<size_t> identity;
×
518
    identity.reserve(keep_indices.size());
×
519
    for (size_t k : keep_indices) identity.push_back(k);
×
520
    return std::make_unique<IndexSelector>(std::move(identity));
×
521
}
×
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