Coverage Report

Created: 2026-07-07 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/duckdb/src/storage/table/table_statistics.cpp
Line
Count
Source
1
#include "duckdb/storage/table/table_statistics.hpp"
2
3
#include "duckdb/common/serializer/deserializer.hpp"
4
#include "duckdb/common/serializer/serializer.hpp"
5
#include "duckdb/execution/reservoir_sample.hpp"
6
#include "duckdb/storage/table/persistent_table_data.hpp"
7
#include "duckdb/parser/column_list.hpp"
8
9
namespace duckdb {
10
11
0
void TableStatistics::Initialize(const vector<LogicalType> &types, PersistentTableData &data) {
12
0
  D_ASSERT(Empty());
13
0
  D_ASSERT(!table_sample);
14
15
0
  stats_lock = make_shared_ptr<mutex>();
16
0
  column_stats = std::move(data.table_stats.column_stats);
17
0
  if (data.table_stats.table_sample) {
18
0
    table_sample = std::move(data.table_stats.table_sample);
19
0
  } else {
20
0
    table_sample = make_uniq<ReservoirSample>(static_cast<idx_t>(FIXED_SAMPLE_SIZE));
21
0
  }
22
0
  if (column_stats.size() != types.size()) { // LCOV_EXCL_START
23
0
    throw IOException("Table statistics column count is not aligned with table column count. Corrupt file?");
24
0
  } // LCOV_EXCL_STOP
25
0
}
26
27
0
void TableStatistics::InitializeEmpty(const TableStatistics &other) {
28
0
  D_ASSERT(Empty());
29
0
  D_ASSERT(!table_sample);
30
31
0
  stats_lock = make_shared_ptr<mutex>();
32
0
  if (other.table_sample) {
33
0
    D_ASSERT(other.table_sample->type == SampleType::RESERVOIR_SAMPLE);
34
0
    auto &res = other.table_sample->Cast<ReservoirSample>();
35
0
    table_sample = res.Copy();
36
0
  } else {
37
0
    table_sample = make_uniq<ReservoirSample>(static_cast<idx_t>(FIXED_SAMPLE_SIZE));
38
0
  }
39
40
0
  for (auto &stats : other.column_stats) {
41
0
    auto new_column_stats = ColumnStatistics::CreateEmptyStats(stats->Statistics().GetType());
42
0
    if (stats->HasDistinctStats()) {
43
0
      new_column_stats->SetDistinct(stats->DistinctStats().Copy());
44
0
    }
45
46
0
    auto &base_stats = new_column_stats->Statistics();
47
0
    if (new_column_stats->HasDistinctStats()) {
48
0
      base_stats.SetDistinctCount(new_column_stats->DistinctStats().GetCount());
49
0
    }
50
0
    column_stats.push_back(new_column_stats);
51
0
  }
52
0
}
53
54
0
void TableStatistics::InitializeEmpty(const vector<LogicalType> &types) {
55
0
  D_ASSERT(Empty());
56
0
  D_ASSERT(!table_sample);
57
58
0
  stats_lock = make_shared_ptr<mutex>();
59
0
  table_sample = make_uniq<ReservoirSample>(static_cast<idx_t>(FIXED_SAMPLE_SIZE));
60
0
  for (auto &type : types) {
61
0
    column_stats.push_back(ColumnStatistics::CreateEmptyStats(type));
62
0
  }
63
0
}
64
65
0
void TableStatistics::InitializeAddColumn(TableStatistics &parent, const LogicalType &new_column_type) {
66
0
  D_ASSERT(Empty());
67
0
  D_ASSERT(parent.stats_lock);
68
69
0
  stats_lock = parent.stats_lock;
70
0
  lock_guard<mutex> lock(*stats_lock);
71
0
  for (idx_t i = 0; i < parent.column_stats.size(); i++) {
72
0
    column_stats.push_back(parent.column_stats[i]);
73
0
  }
74
0
  column_stats.push_back(ColumnStatistics::CreateEmptyStats(new_column_type));
75
0
  if (parent.table_sample) {
76
0
    table_sample = std::move(parent.table_sample);
77
0
  }
78
0
  if (table_sample) {
79
0
    table_sample->Destroy();
80
0
  }
81
0
}
82
83
0
void TableStatistics::InitializeRemoveColumn(TableStatistics &parent, idx_t removed_column) {
84
0
  D_ASSERT(Empty());
85
0
  D_ASSERT(parent.stats_lock);
86
87
0
  stats_lock = parent.stats_lock;
88
0
  lock_guard<mutex> lock(*stats_lock);
89
0
  for (idx_t i = 0; i < parent.column_stats.size(); i++) {
90
0
    if (i != removed_column) {
91
0
      column_stats.push_back(parent.column_stats[i]);
92
0
    }
93
0
  }
94
0
  if (parent.table_sample) {
95
0
    table_sample = std::move(parent.table_sample);
96
0
  }
97
0
  if (table_sample) {
98
0
    table_sample->Destroy();
99
0
  }
100
0
}
101
102
0
void TableStatistics::InitializeAlterType(TableStatistics &parent, idx_t changed_idx, const LogicalType &new_type) {
103
0
  D_ASSERT(Empty());
104
0
  D_ASSERT(parent.stats_lock);
105
106
0
  stats_lock = parent.stats_lock;
107
0
  lock_guard<mutex> lock(*stats_lock);
108
0
  for (idx_t i = 0; i < parent.column_stats.size(); i++) {
109
0
    if (i == changed_idx) {
110
0
      column_stats.push_back(ColumnStatistics::CreateEmptyStats(new_type));
111
0
    } else {
112
0
      column_stats.push_back(parent.column_stats[i]);
113
0
    }
114
0
  }
115
0
  if (parent.table_sample) {
116
0
    table_sample = std::move(parent.table_sample);
117
0
  }
118
0
  if (table_sample) {
119
0
    table_sample->Destroy();
120
0
  }
121
0
}
122
123
0
void TableStatistics::InitializeAddConstraint(TableStatistics &parent) {
124
0
  D_ASSERT(Empty());
125
0
  D_ASSERT(parent.stats_lock);
126
127
0
  stats_lock = parent.stats_lock;
128
0
  lock_guard<mutex> lock(*stats_lock);
129
0
  for (idx_t i = 0; i < parent.column_stats.size(); i++) {
130
0
    column_stats.push_back(parent.column_stats[i]);
131
0
  }
132
0
  if (parent.table_sample) {
133
0
    table_sample = std::move(parent.table_sample);
134
0
  }
135
0
}
136
137
0
void TableStatistics::MergeStats(TableStatistics &other) {
138
0
  auto l = GetLock();
139
0
  D_ASSERT(column_stats.size() == other.column_stats.size());
140
0
  if (table_sample) {
141
0
    if (other.table_sample) {
142
0
      D_ASSERT(table_sample->type == SampleType::RESERVOIR_SAMPLE);
143
0
      auto &this_reservoir = table_sample->Cast<ReservoirSample>();
144
0
      D_ASSERT(other.table_sample->type == SampleType::RESERVOIR_SAMPLE);
145
0
      this_reservoir.Merge(std::move(other.table_sample));
146
0
    }
147
    // if no other.table sample, do nothing
148
0
  } else {
149
0
    if (other.table_sample) {
150
0
      auto &other_reservoir = other.table_sample->Cast<ReservoirSample>();
151
0
      auto other_table_sample_copy = other_reservoir.Copy();
152
0
      table_sample = std::move(other_table_sample_copy);
153
0
    }
154
0
  }
155
0
  for (idx_t i = 0; i < column_stats.size(); i++) {
156
0
    if (column_stats[i]) {
157
0
      D_ASSERT(other.column_stats[i]);
158
0
      column_stats[i]->Merge(*other.column_stats[i]);
159
0
    }
160
0
  }
161
0
}
162
163
0
void TableStatistics::MergeStats(idx_t i, BaseStatistics &stats) {
164
0
  auto l = GetLock();
165
0
  MergeStats(*l, i, stats);
166
0
}
167
168
0
void TableStatistics::MergeStats(TableStatisticsLock &lock, idx_t i, BaseStatistics &stats, StatsMergeType merge_type) {
169
0
  column_stats[i]->Statistics().Merge(stats, merge_type);
170
0
}
171
172
0
ColumnStatistics &TableStatistics::GetStats(TableStatisticsLock &lock, idx_t i) {
173
0
  return *column_stats[i];
174
0
}
175
176
// BlockingSample &TableStatistics::GetTableSampleRef(TableStatisticsLock &lock) {
177
//  D_ASSERT(table_sample);
178
//  return *table_sample;
179
//}
180
181
0
unique_ptr<BlockingSample> TableStatistics::GetTableSample(TableStatisticsLock &lock) {
182
0
  return std::move(table_sample);
183
0
}
184
185
0
void TableStatistics::SetTableSample(TableStatisticsLock &lock, unique_ptr<BlockingSample> sample) {
186
0
  table_sample = std::move(sample);
187
0
}
188
189
0
void TableStatistics::DestroyTableSample(TableStatisticsLock &lock) const {
190
0
  if (table_sample) {
191
0
    table_sample->Destroy();
192
0
  }
193
0
}
194
195
0
void TableStatistics::SetStats(TableStatistics &other) {
196
0
  TableStatisticsLock lock(*stats_lock);
197
0
  column_stats = std::move(other.column_stats);
198
0
  table_sample = std::move(other.table_sample);
199
0
}
200
201
0
unique_ptr<BaseStatistics> TableStatistics::CopyStats(const StorageIndex &index) {
202
0
  lock_guard<mutex> l(*stats_lock);
203
204
0
  auto column_index = index.GetPrimaryIndex();
205
0
  auto &stats = *column_stats[column_index];
206
0
  auto result = stats.Statistics().Copy();
207
0
  if (stats.HasDistinctStats()) {
208
0
    result.SetDistinctCount(stats.DistinctStats().GetCount());
209
0
  }
210
0
  if (index.IsPushdownExtract()) {
211
0
    return result.PushdownExtract(index.GetChildIndexes()[0]);
212
0
  }
213
0
  return result.ToUnique();
214
0
}
215
216
0
void TableStatistics::CopyStats(TableStatistics &other) {
217
0
  TableStatisticsLock lock(*stats_lock);
218
0
  CopyStats(lock, other);
219
0
}
220
221
0
void TableStatistics::CopyStats(TableStatisticsLock &lock, TableStatistics &other) {
222
0
  D_ASSERT(other.Empty());
223
0
  other.stats_lock = make_shared_ptr<mutex>();
224
0
  for (auto &stats : column_stats) {
225
0
    other.column_stats.push_back(stats->Copy());
226
0
  }
227
228
0
  if (table_sample) {
229
0
    D_ASSERT(table_sample->type == SampleType::RESERVOIR_SAMPLE);
230
0
    auto &res = table_sample->Cast<ReservoirSample>();
231
0
    other.table_sample = res.Copy();
232
0
  }
233
0
}
234
235
0
void TableStatistics::Serialize(Serializer &serializer) const {
236
0
  serializer.WriteProperty(100, "column_stats", column_stats);
237
0
  unique_ptr<BlockingSample> to_serialize = nullptr;
238
0
  if (table_sample) {
239
0
    D_ASSERT(table_sample->type == SampleType::RESERVOIR_SAMPLE);
240
0
    auto &reservoir_sample = table_sample->Cast<ReservoirSample>();
241
0
    to_serialize = unique_ptr_cast<BlockingSample, ReservoirSample>(reservoir_sample.Copy());
242
0
    auto &res_serialize = to_serialize->Cast<ReservoirSample>();
243
0
    res_serialize.EvictOverBudgetSamples();
244
0
  }
245
0
  serializer.WritePropertyWithDefault<unique_ptr<BlockingSample>>(101, "table_sample", to_serialize, nullptr);
246
0
}
247
248
0
void TableStatistics::Deserialize(Deserializer &deserializer, ColumnList &columns) {
249
0
  auto physical_columns = columns.Physical();
250
251
0
  auto iter = physical_columns.begin();
252
0
  deserializer.ReadList(100, "column_stats", [&](Deserializer::List &list, idx_t i) {
253
0
    auto &col = *iter;
254
0
    iter.operator++();
255
256
0
    auto type = col.GetType();
257
0
    deserializer.Set<LogicalType &>(type);
258
259
0
    column_stats.push_back(list.ReadElement<shared_ptr<ColumnStatistics>>());
260
261
0
    deserializer.Unset<LogicalType>();
262
0
  });
263
0
  table_sample = deserializer.ReadPropertyWithDefault<unique_ptr<BlockingSample>>(101, "table_sample");
264
0
  if (table_sample) {
265
0
    D_ASSERT(table_sample->type == SampleType::RESERVOIR_SAMPLE);
266
#ifdef DEBUG
267
    if (table_sample) {
268
      auto &reservoir_sample = table_sample->Cast<ReservoirSample>();
269
      reservoir_sample.Verify();
270
    }
271
#endif
272
0
  } else {
273
0
    table_sample = make_uniq<ReservoirSample>(static_cast<idx_t>(FIXED_SAMPLE_SIZE));
274
0
    table_sample->Destroy();
275
0
  }
276
0
}
277
278
0
unique_ptr<TableStatisticsLock> TableStatistics::GetLock() {
279
0
  D_ASSERT(stats_lock);
280
0
  return make_uniq<TableStatisticsLock>(*stats_lock);
281
0
}
282
283
0
bool TableStatistics::Empty() const {
284
0
  D_ASSERT(column_stats.empty() == (stats_lock.get() == nullptr));
285
0
  return column_stats.empty();
286
0
}
287
288
} // namespace duckdb