morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
sort.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file table/sort.hpp
6/// @brief Typed sort keys and the chunked, stoppable merge sort over them.
7///
8/// Each column a sort or filter uses gets a `KeyColumn`: one typed array of
9/// keys plus a `CellState` per cell, built once from a snapshot. Comparing two
10/// rows is then a direct compare of two keys, with no parsing and no
11/// allocation. Exact kinds stay exact: integers, dates and booleans compare as
12/// `int64`, decimals and quantities as `Rational` (128-bit cross products);
13/// only a `Number` column holds a double.
14///
15/// Ordering rules, normative for every client and server (spec 7 ยง5):
16/// - an empty or invalid cell sorts after every valid one, in either direction;
17/// - each key of a chain has its own direction;
18/// - ties break by source row, so the sort is stable.
19///
20/// See `docs/spec/table/engine.md`.
21
22#include <algorithm>
23#include <array>
24#include <cctype>
25#include <charconv>
26#include <chrono>
27#include <cmath>
28#include <compare>
29#include <core/async/StopToken.hpp>
30#include <cstddef>
31#include <cstdint>
32#include <ios>
33#include <locale>
34#include <memory>
36#include <optional>
37#include <span>
38#include <sstream>
39#include <string>
40#include <string_view>
41#include <system_error>
42#include <utility>
43#include <variant>
44#include <vector>
45#include <version>
46
47namespace morph::table {
48
49// NOLINTBEGIN(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- row and column indices are bounded by the snapshot's own counts, and the sort and filter loops index once per comparison, where at() would check every access
50
51/// @brief A sort key's direction.
52enum class SortDirection : std::uint8_t {
53 Ascending, ///< Smallest first.
54 Descending, ///< Largest first. Invalid cells still come last.
55};
56
57/// @brief One key of a sort chain.
58struct SortKey {
59 /// @brief The column's id.
60 std::string column;
61 /// @brief The key's direction.
63
64 /// @brief Member-wise equality.
65 /// @param other The key to compare with.
66 /// @return `true` when column and direction match.
67 [[nodiscard]] bool operator==(SortKey const& other) const = default;
68};
69
70/// @brief A multi-key sort, most significant key first. Empty means source order.
71using SortChain = std::vector<SortKey>;
72
73/// @brief Whether a cell has a key.
74enum class CellState : std::uint8_t {
75 Valid, ///< The cell has a key.
76 Empty, ///< The cell is empty; it sorts with invalid cells.
77 Invalid, ///< The cell could not be read for its column's kind.
78};
79
80/// @brief A deadline for one step of work; `kNoDeadline` runs to completion.
81using Deadline = std::chrono::steady_clock::time_point;
82
83/// @brief The deadline that never passes.
84inline constexpr Deadline kNoDeadline = Deadline::max();
85
86namespace detail {
87
88/// @brief Elements of work between two looks at the clock and the stop token:
89/// often enough to keep an owner step near its budget, rarely enough
90/// that the clock read is noise.
91inline constexpr std::size_t kCheckEvery = 2048;
92
93/// @brief Whether a step should return now.
94/// @param deadline When the step must end; `kNoDeadline` never passes.
95/// @param stop A stop request ends the step.
96/// @return `true` when stop is requested or the deadline has passed.
97[[nodiscard]] inline bool shouldYield(Deadline deadline, ::core::async::StopToken const& stop) {
98 return stop.stop_requested() || (deadline != kNoDeadline && std::chrono::steady_clock::now() >= deadline);
99}
100
101/// @brief The collator to use: the service's, or the default.
102/// @param services The services.
103/// @return The collator; the default outlives every caller.
104[[nodiscard]] inline TextCollator const& collatorOf(Services const& services) {
105 return services.collator != nullptr ? *services.collator : *services.collatorOrDefault();
106}
107
108/// @brief The date parser to use: the service's, or the default.
109/// @param services The services.
110/// @return The parser; the default outlives every caller.
111[[nodiscard]] inline DateParser const& datesOf(Services const& services) {
112 return services.dates != nullptr ? *services.dates : *services.datesOrDefault();
113}
114
115/// @brief Parses a whole decimal integer, all of @p text.
116/// @param text The text.
117/// @return The value, or nothing when @p text is not exactly an integer.
118[[nodiscard]] inline std::optional<std::int64_t> parseInteger(std::string_view text) {
119 std::int64_t value = 0;
120 auto const* const first = std::to_address(text.begin());
121 auto const* const last = std::to_address(text.end());
122 auto const [ptr, ec] = std::from_chars(first, last, value);
123 if (ec != std::errc{} || ptr != last || text.empty()) {
124 return std::nullopt;
125 }
126 return value;
127}
128
129/// @brief Parses a floating-point number, all of @p text, through a stream in the classic locale: the fallback of
130/// `parseReal` for a standard library without floating-point `from_chars`.
131///
132/// The classic locale, not the global one: a filter value is the wire's text, so its decimal point is `.` whatever
133/// locale the process runs in.
134/// @param text The text.
135/// @return The value, or nothing when @p text is not exactly a number or is NaN.
136[[nodiscard]] inline std::optional<double> parseRealClassic(std::string_view text) {
137 if (text.empty() || text.front() == '+' || std::isspace(static_cast<unsigned char>(text.front())) != 0) {
138 return std::nullopt;
139 }
140 std::istringstream stream{std::string{text}};
141 stream.imbue(std::locale::classic());
142 double value = 0;
143 stream >> std::noskipws >> value;
144 if (stream.fail() || stream.peek() != std::char_traits<char>::eof() || std::isnan(value)) {
145 return std::nullopt;
146 }
147 return value;
148}
149
150/// @brief Parses a floating-point number, all of @p text.
151/// @param text The text.
152/// @return The value, or nothing when @p text is not exactly a number or is NaN.
153[[nodiscard]] inline std::optional<double> parseReal(std::string_view text) {
154#if defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L
155 double value = 0;
156 auto const* const first = std::to_address(text.begin());
157 auto const* const last = std::to_address(text.end());
158 auto const [ptr, ec] = std::from_chars(first, last, value);
159 if (ec != std::errc{} || ptr != last || text.empty() || std::isnan(value)) {
160 return std::nullopt;
161 }
162 return value;
163#else
164 // libc++ before floating-point from_chars, such as Emscripten's.
165 return parseRealClassic(text);
166#endif
167}
168
169} // namespace detail
170
171/// @brief One column's keys, built once and shared, immutable, between jobs.
172///
173/// Exactly one of the typed arrays is filled, chosen by the column's kind:
174/// `ints` for `Integer`, `Key`, `Date`, `DateTime` and `Bool`; `exact` for
175/// `Decimal` and `Quantity`; `reals` for `Number`; `text`, `folded` and `raw`
176/// for `Text`; `cells` for a `Custom` column with a known comparator, and
177/// `text` for one without (ordered by display text).
178struct KeyColumn {
179 /// @brief The column's kind.
181 /// @brief The state of each row's cell.
182 std::vector<CellState> states;
183 /// @brief Integer keys.
184 std::vector<std::int64_t> ints;
185 /// @brief Exact keys.
186 std::vector<math::Rational> exact;
187 /// @brief Floating-point keys.
188 std::vector<double> reals;
189 /// @brief Collation keys.
190 std::vector<std::string> text;
191 /// @brief Primary-strength folds, which text filters match.
192 std::vector<std::string> folded;
193 /// @brief The text as the source gave it, for case-sensitive filters.
194 std::vector<std::string> raw;
195 /// @brief Cells of a `Custom` column with a comparator.
196 std::vector<Cell> cells;
197 /// @brief The comparator of a `Custom` column, or empty.
199
200 /// @brief Whether this column orders `Custom` cells through a comparator.
201 /// @return `true` when `comparator` is set.
202 [[nodiscard]] bool usesComparator() const noexcept { return static_cast<bool>(comparator); }
203
204 /// @brief An empty key column for @p info, sized for @p rows rows.
205 /// @param info The column.
206 /// @param services Where a `Custom` column's comparator is looked up.
207 /// @param rows Row count.
208 /// @return The column, every cell `Empty` until built.
209 [[nodiscard]] static KeyColumn forColumn(ColumnInfo const& info, Services const& services, std::size_t rows) {
210 KeyColumn out;
211 out.kind = info.kind;
212 out.states.assign(rows, CellState::Empty);
213 switch (info.kind) {
215 case ColumnKind::Key:
216 case ColumnKind::Date:
218 case ColumnKind::Bool:
219 out.ints.assign(rows, 0);
220 break;
223 out.exact.assign(rows, math::Rational{});
224 break;
226 out.reals.assign(rows, 0.0);
227 break;
228 case ColumnKind::Text:
229 out.text.resize(rows);
230 out.folded.resize(rows);
231 out.raw.resize(rows);
232 break;
234 if (auto const found = services.comparators.find(info.comparator);
235 found != services.comparators.end() && found->second) {
236 out.comparator = found->second;
237 out.cells.resize(rows);
238 } else {
239 out.text.resize(rows);
240 }
241 break;
242 default:
243 break;
244 }
245 return out;
246 }
247
248 /// @brief Rebuilds the key of one row from @p cell.
249 /// @param row Row index.
250 /// @param cell The row's cell.
251 /// @param info The column.
252 /// @param services Collator and date parser; a null member means its default.
253 void set(std::size_t row, Cell const& cell, ColumnInfo const& info, Services const& services) {
254 states[row] = read(row, cell, info, services);
255 }
256
257private:
258 [[nodiscard]] CellState read(std::size_t row, Cell const& cell, ColumnInfo const& info, Services const& services) {
259 if (std::holds_alternative<std::monostate>(cell)) {
260 return CellState::Empty;
261 }
262 auto const* const textCell = std::get_if<std::string>(&cell);
263 if (textCell != nullptr && textCell->empty()) {
264 return CellState::Empty;
265 }
266 switch (kind) {
267 case ColumnKind::Integer:
268 case ColumnKind::Key:
269 return readInteger(row, cell);
270 case ColumnKind::Decimal:
271 case ColumnKind::Quantity:
272 return readExact(row, cell);
273 case ColumnKind::Number:
274 return readReal(row, cell);
275 case ColumnKind::Text: {
276 auto const shown = textCell != nullptr ? *textCell : displayText(cell);
277 auto const& collator = detail::collatorOf(services);
278 text[row] = collator.sortKey(shown);
279 folded[row] = collator.fold(shown);
280 raw[row] = shown;
281 return CellState::Valid;
282 }
283 case ColumnKind::Date:
284 case ColumnKind::DateTime:
285 return readDate(row, cell, info, services);
286 case ColumnKind::Bool:
287 return readBool(row, cell);
288 case ColumnKind::Custom:
289 if (usesComparator()) {
290 cells[row] = cell;
291 } else {
292 text[row] = detail::collatorOf(services).sortKey(displayText(cell));
293 }
294 return CellState::Valid;
295 default:
296 break;
297 }
298 return CellState::Invalid;
299 }
300
301 [[nodiscard]] CellState readInteger(std::size_t row, Cell const& cell) {
302 if (auto const* value = std::get_if<std::int64_t>(&cell)) {
303 ints[row] = *value;
304 return CellState::Valid;
305 }
306 if (auto const* value = std::get_if<math::Rational>(&cell); value != nullptr && value->isInteger()) {
307 ints[row] = value->numerator;
308 return CellState::Valid;
309 }
310 if (auto const* value = std::get_if<std::string>(&cell)) {
311 if (auto const parsed = detail::parseInteger(*value)) {
312 ints[row] = *parsed;
313 return CellState::Valid;
314 }
315 }
316 return CellState::Invalid;
317 }
318
319 [[nodiscard]] CellState readExact(std::size_t row, Cell const& cell) {
320 if (auto const* value = std::get_if<math::Rational>(&cell)) {
321 exact[row] = *value;
322 return CellState::Valid;
323 }
324 if (auto const* value = std::get_if<QuantityCell>(&cell)) {
325 // Checked: a product that does not fit int64 is an invalid cell, not
326 // a saturated value that would sort as the largest quantity.
327 auto const canonical = math::checkedMul(value->amount, value->toCanonical);
328 if (!canonical) {
329 return CellState::Invalid;
330 }
331 exact[row] = *canonical;
332 return CellState::Valid;
333 }
334 if (auto const* value = std::get_if<std::int64_t>(&cell)) {
335 exact[row] = math::Rational{*value, math::DecimalPlaces{0}};
336 return CellState::Valid;
337 }
338 if (auto const* value = std::get_if<std::string>(&cell)) {
339 if (auto const parsed = parseDecimal(*value)) {
340 exact[row] = *parsed;
341 return CellState::Valid;
342 }
343 }
344 return CellState::Invalid;
345 }
346
347 [[nodiscard]] CellState readReal(std::size_t row, Cell const& cell) {
348 std::optional<double> value;
349 if (auto const* real = std::get_if<double>(&cell)) {
350 value = *real;
351 } else if (auto const* integer = std::get_if<std::int64_t>(&cell)) {
352 value = static_cast<double>(*integer);
353 } else if (auto const* exactValue = std::get_if<math::Rational>(&cell)) {
354 value = exactValue->toDouble(math::kMaxDecimalPlaces + 1);
355 } else if (auto const* textValue = std::get_if<std::string>(&cell)) {
356 value = detail::parseReal(*textValue);
357 }
358 if (!value || std::isnan(*value)) {
359 return CellState::Invalid;
360 }
361 reals[row] = *value;
362 return CellState::Valid;
363 }
364
365 [[nodiscard]] CellState readDate(std::size_t row, Cell const& cell, ColumnInfo const& info,
366 Services const& services) {
367 if (auto const* value = std::get_if<std::int64_t>(&cell)) {
368 ints[row] = *value;
369 return CellState::Valid;
370 }
371 if (auto const* value = std::get_if<std::string>(&cell)) {
372 auto const& parser = detail::datesOf(services);
373 auto const parsed = kind == ColumnKind::Date ? parser.parseDate(*value, info.format)
374 : parser.parseDateTime(*value, info.format);
375 if (parsed) {
376 ints[row] = *parsed;
377 return CellState::Valid;
378 }
379 }
380 return CellState::Invalid;
381 }
382
383 [[nodiscard]] CellState readBool(std::size_t row, Cell const& cell) {
384 if (auto const* value = std::get_if<bool>(&cell)) {
385 ints[row] = *value ? 1 : 0;
386 return CellState::Valid;
387 }
388 if (auto const* value = std::get_if<std::int64_t>(&cell); value != nullptr && (*value == 0 || *value == 1)) {
389 ints[row] = *value;
390 return CellState::Valid;
391 }
392 if (auto const* value = std::get_if<std::string>(&cell);
393 value != nullptr && (*value == "true" || *value == "false")) {
394 ints[row] = *value == "true" ? 1 : 0;
395 return CellState::Valid;
396 }
397 return CellState::Invalid;
398 }
399};
400
401/// @brief Builds the keys of rows `[first, last)` of one column into @p out.
402/// @param snapshot The rows.
403/// @param column Column index in the snapshot.
404/// @param info The column.
405/// @param services Collator and date parser; a null member means its default.
406/// @param first First row.
407/// @param last One past the last row.
408/// @param out A column from `KeyColumn::forColumn` sized for the snapshot.
409inline void buildKeys(RowSnapshot const& snapshot, std::size_t column, ColumnInfo const& info,
410 Services const& services, std::size_t first, std::size_t last, KeyColumn& out) {
411 struct Sink final : ColumnSink {
412 Sink(KeyColumn& keys, ColumnInfo const& column, Services const& with)
413 : out{&keys}, info{&column}, services{&with} {}
414 void cells(std::size_t firstRow, std::span<Cell const> run) override {
415 for (std::size_t i = 0; i < run.size(); ++i) {
416 out->set(firstRow + i, run[i], *info, *services);
417 }
418 }
419 KeyColumn* out;
420 ColumnInfo const* info;
421 Services const* services;
422 };
423 Sink sink{out, info, services};
424 snapshot.readColumn(column, sink, first, last);
425}
426
427/// @brief Compares two rows' keys in one column, valid before invalid in
428/// either direction.
429/// @param keys The column's keys.
430/// @param left One row.
431/// @param right Another row.
432/// @param dir The key's direction; it does not move invalid cells.
433/// @return Negative, zero or positive as row @p left orders before, with, or after @p right.
434[[nodiscard]] inline int compareKeys(KeyColumn const& keys, std::uint32_t left, std::uint32_t right,
435 SortDirection dir) {
436 bool const validA = keys.states[left] == CellState::Valid;
437 bool const validB = keys.states[right] == CellState::Valid;
438 if (validA != validB) {
439 return validA ? -1 : 1;
440 }
441 if (!validA) {
442 return 0;
443 }
444 auto const sign = [](auto ordering) -> int {
445 if (std::is_lt(ordering)) {
446 return -1;
447 }
448 return std::is_gt(ordering) ? 1 : 0;
449 };
450 int result = 0;
451 switch (keys.kind) {
453 case ColumnKind::Key:
454 case ColumnKind::Date:
456 case ColumnKind::Bool:
457 result = sign(keys.ints[left] <=> keys.ints[right]);
458 break;
461 result = sign(keys.exact[left] <=> keys.exact[right]);
462 break;
464 result = sign(std::strong_order(keys.reals[left], keys.reals[right]));
465 break;
466 case ColumnKind::Text:
467 result = sign(keys.text[left].compare(keys.text[right]) <=> 0);
468 break;
470 result = keys.usesComparator() ? sign(keys.comparator(keys.cells[left], keys.cells[right]))
471 : sign(keys.text[left].compare(keys.text[right]) <=> 0);
472 break;
473 default:
474 break;
475 }
476 return dir == SortDirection::Descending ? -result : result;
477}
478
479/// @brief A sort chain resolved to key columns: the comparator of a sort.
480///
481/// Holds pointers to key columns it does not own; whoever builds it keeps the
482/// columns alive for as long as it is used.
483class RowOrder {
484public:
485 /// @brief One resolved key.
486 struct Key {
487 /// @brief The column's keys.
488 KeyColumn const* keys = nullptr;
489 /// @brief The key's direction.
491 };
492
493 /// @brief Source order.
494 RowOrder() = default;
495
496 /// @brief An order over resolved keys, most significant first.
497 /// @param keys The keys.
498 explicit RowOrder(std::vector<Key> keys) : _keys{std::move(keys)} {}
499
500 /// @brief Whether row @p a orders before row @p b. Ties break by source
501 /// row, so this is a strict total order and every sort is stable.
502 /// @param left One row.
503 /// @param right Another row.
504 /// @return `true` when @p left comes first.
505 [[nodiscard]] bool operator()(std::uint32_t left, std::uint32_t right) const {
506 for (auto const& key : _keys) {
507 if (int const result = compareKeys(*key.keys, left, right, key.dir); result != 0) {
508 return result < 0;
509 }
510 }
511 return left < right;
512 }
513
514 /// @brief Whether this is source order.
515 /// @return `true` when no key is set.
516 [[nodiscard]] bool empty() const noexcept { return _keys.empty(); }
517
518private:
519 std::vector<Key> _keys;
520};
521
522/// @brief A bottom-up merge sort over row indices that can stop and resume.
523///
524/// Runs of `kRun` rows are sorted first, then merged pass by pass. `run`
525/// returns at a deadline or a stop request and continues from there on the
526/// next call, which is how the engine sorts 100,000 rows on an owner thread
527/// in frame-sized steps, or abandons a superseded sort on a worker.
529public:
530 /// @brief Rows per initial run.
531 static constexpr std::size_t kRun = 512;
532
533 /// @brief Prepares to sort @p rows by @p order.
534 /// @param rows Row indices to sort.
535 /// @param order The comparator; its key columns must outlive the sort.
536 ChunkedMergeSort(std::vector<std::uint32_t> rows, RowOrder order)
537 : _rows{std::move(rows)}, _order{std::move(order)} {
538 _buffer.resize(_rows.size());
539 }
540
541 /// @brief Sorts until done, the deadline passes, or a stop is requested.
542 /// @param deadline When to yield.
543 /// @param stop A stop request ends the step early.
544 /// @return `true` when the rows are sorted.
545 bool run(Deadline deadline, ::core::async::StopToken const& stop) {
546 return sortRuns(deadline, stop) && mergePasses(deadline, stop);
547 }
548
549 /// @brief Whether the sort has finished.
550 /// @return `true` once `run` returned `true`.
551 [[nodiscard]] bool done() const noexcept { return _runStart >= _rows.size() && _width >= _rows.size(); }
552
553 /// @brief The sorted rows. Valid once `done()`.
554 /// @return The rows, moved out.
555 [[nodiscard]] std::vector<std::uint32_t> take() && { return std::move(_rows); }
556
557private:
558 // Phase one: sorts each run of kRun rows in place.
559 bool sortRuns(Deadline deadline, ::core::async::StopToken const& stop) {
560 auto const count = _rows.size();
561 std::size_t work = 0;
562 while (_runStart < count) {
563 auto const end = std::min(_runStart + kRun, count);
564 std::sort(_rows.begin() + static_cast<std::ptrdiff_t>(_runStart),
565 _rows.begin() + static_cast<std::ptrdiff_t>(end), _order);
566 work += end - _runStart;
567 _runStart = end;
568 if (work >= detail::kCheckEvery && detail::shouldYield(deadline, stop)) {
569 return false;
570 }
571 }
572 return true;
573 }
574
575 // Phase two: merges runs pass by pass, doubling their width.
576 bool mergePasses(Deadline deadline, ::core::async::StopToken const& stop) {
577 auto const count = _rows.size();
578 while (_width < count) {
579 while (_mergeAt < count) {
580 if (!mergeOne(deadline, stop)) {
581 return false;
582 }
583 }
584 _rows.swap(_buffer);
585 _width *= 2;
586 _mergeAt = 0;
587 if (_width < count && detail::shouldYield(deadline, stop)) {
588 return false;
589 }
590 }
591 return true;
592 }
593
594 // Merges the two runs at `_mergeAt` into the buffer, resuming a merge a
595 // previous step left half done.
596 bool mergeOne(Deadline deadline, ::core::async::StopToken const& stop) {
597 auto const count = _rows.size();
598 if (!_merging) {
599 _left = _mergeAt;
600 _leftEnd = std::min(_mergeAt + _width, count);
601 _right = _leftEnd;
602 _rightEnd = std::min(_mergeAt + (2 * _width), count);
603 _out = _mergeAt;
604 _merging = true;
605 }
606 // The count runs across merges: a pass of narrow runs merges many
607 // short pairs, and a per-merge count would never reach a check.
608 while (_left < _leftEnd && _right < _rightEnd) {
609 _buffer[_out++] = _order(_rows[_right], _rows[_left]) ? _rows[_right++] : _rows[_left++];
610 if (++_work % detail::kCheckEvery == 0 && detail::shouldYield(deadline, stop)) {
611 return false;
612 }
613 }
614 std::copy(_rows.begin() + static_cast<std::ptrdiff_t>(_left),
615 _rows.begin() + static_cast<std::ptrdiff_t>(_leftEnd),
616 _buffer.begin() + static_cast<std::ptrdiff_t>(_out));
617 _out += _leftEnd - _left;
618 _left = _leftEnd;
619 std::copy(_rows.begin() + static_cast<std::ptrdiff_t>(_right),
620 _rows.begin() + static_cast<std::ptrdiff_t>(_rightEnd),
621 _buffer.begin() + static_cast<std::ptrdiff_t>(_out));
622 _out += _rightEnd - _right;
623 _right = _rightEnd;
624 _merging = false;
625 _mergeAt = _rightEnd;
626 return true;
627 }
628
629 std::vector<std::uint32_t> _rows;
630 std::vector<std::uint32_t> _buffer;
631 RowOrder _order;
632 std::size_t _runStart = 0;
633 std::size_t _width = kRun;
634 std::size_t _mergeAt = 0;
635 bool _merging = false;
636 std::size_t _left = 0;
637 std::size_t _leftEnd = 0;
638 std::size_t _right = 0;
639 std::size_t _rightEnd = 0;
640 std::size_t _out = 0;
641 std::size_t _work = 0;
642};
643
644/// @brief Sorts @p rows by @p order in one call.
645/// @param rows Row indices.
646/// @param order The comparator.
647/// @return The sorted rows.
648[[nodiscard]] inline std::vector<std::uint32_t> sortRows(std::vector<std::uint32_t> rows, RowOrder order) {
649 ChunkedMergeSort sort{std::move(rows), std::move(order)};
650 static_cast<void>(sort.run(kNoDeadline, ::core::async::StopToken{}));
651 return std::move(sort).take();
652}
653
654// NOLINTEND(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access)
655
656} // namespace morph::table
657
658/// @brief JSON names of `morph::table::SortDirection`: `asc` and `desc`.
659template <>
660struct glz::meta<morph::table::SortDirection> {
662 /// @brief Each enumerator after its JSON name.
663 static constexpr auto value = glz::enumerate("asc", Ascending, "desc", Descending);
664};
Handle< T > row()
The innermost row, {"row": ""}.
Definition author.hpp:1145
Handle< std::int64_t > count(AnyHandle const &list)
count: a list's length.
Definition author.hpp:1000
A bottom-up merge sort over row indices that can stop and resume.
Definition sort.hpp:528
static constexpr std::size_t kRun
Rows per initial run.
Definition sort.hpp:531
ChunkedMergeSort(std::vector< std::uint32_t > rows, RowOrder order)
Prepares to sort rows by order.
Definition sort.hpp:536
std::vector< std::uint32_t > take() &&
The sorted rows. Valid once done().
Definition sort.hpp:555
bool done() const noexcept
Whether the sort has finished.
Definition sort.hpp:551
bool run(Deadline deadline, ::core::async::StopToken const &stop)
Sorts until done, the deadline passes, or a stop is requested.
Definition sort.hpp:545
Receives a run of one column's cells.
Parses text to dates and date-times for Date and DateTime columns that hold text, and for filter valu...
A sort chain resolved to key columns: the comparator of a sort.
Definition sort.hpp:483
bool empty() const noexcept
Whether this is source order.
Definition sort.hpp:516
RowOrder()=default
Source order.
RowOrder(std::vector< Key > keys)
An order over resolved keys, most significant first.
Definition sort.hpp:498
bool operator()(std::uint32_t left, std::uint32_t right) const
Whether row a orders before row b. Ties break by source row, so this is a strict total order and ever...
Definition sort.hpp:505
An immutable view of a table's rows at one moment.
virtual void readColumn(std::size_t column, ColumnSink &sink, std::size_t first, std::size_t last) const
Hands rows [first, last) of column to sink.
Orders and matches text. The renderer injects the toolkit's collator for the user's locale; CodePoint...
The rows a table engine reads, and the services it is given.
std::variant< std::monostate, std::int64_t, math::Rational, double, std::string, bool, QuantityCell > Cell
One cell as a data source hands it out.
std::function< std::strong_ordering(Cell const &, Cell const &)> CellComparator
Orders two cells of a Custom column. Called from the worker, so it must be safe to call concurrently.
ColumnKind
What a column holds, which decides how its keys are built and compared.
@ Date
Days since 1970-01-01.
@ Quantity
An exact Rational in the column's canonical unit.
@ Key
An int64 key, compared numerically.
@ DateTime
UTC seconds since 1970-01-01T00:00:00Z.
@ Custom
Ordered by a named comparator from Services::comparators.
@ Decimal
An exact Rational.
@ Text
Text, ordered and matched through the TextCollator.
@ Integer
int64, compared numerically.
@ Number
A double; the only kind that holds one.
@ Bool
false before true.
std::optional< math::Rational > parseDecimal(std::string_view text, NumericLocale const &loc, std::optional< std::uint32_t > maxDecimalPlaces=std::nullopt)
Parses locale-formatted decimal entry text into the exact value it spells.
DateParser const & datesOf(Services const &services)
The date parser to use: the service's, or the default.
Definition sort.hpp:111
int compareKeys(KeyColumn const &keys, std::uint32_t left, std::uint32_t right, SortDirection dir)
Compares two rows' keys in one column, valid before invalid in either direction.
Definition sort.hpp:434
std::vector< SortKey > SortChain
A multi-key sort, most significant key first. Empty means source order.
Definition sort.hpp:71
SortDirection
A sort key's direction.
Definition sort.hpp:52
@ Ascending
Smallest first.
Definition sort.hpp:53
@ Descending
Largest first. Invalid cells still come last.
Definition sort.hpp:54
std::optional< std::int64_t > parseInteger(std::string_view text)
Parses a whole decimal integer, all of text.
Definition sort.hpp:118
std::optional< double > parseRealClassic(std::string_view text)
Parses a floating-point number, all of text, through a stream in the classic locale: the fallback of ...
Definition sort.hpp:136
CellState
Whether a cell has a key.
Definition sort.hpp:74
@ Valid
The cell has a key.
Definition sort.hpp:75
@ Invalid
The cell could not be read for its column's kind.
Definition sort.hpp:77
@ Empty
The cell is empty; it sorts with invalid cells.
Definition sort.hpp:76
constexpr std::size_t kCheckEvery
Elements of work between two looks at the clock and the stop token: often enough to keep an owner ste...
Definition sort.hpp:91
constexpr Deadline kNoDeadline
The deadline that never passes.
Definition sort.hpp:84
std::chrono::steady_clock::time_point Deadline
A deadline for one step of work; kNoDeadline runs to completion.
Definition sort.hpp:81
bool shouldYield(Deadline deadline, ::core::async::StopToken const &stop)
Whether a step should return now.
Definition sort.hpp:97
std::vector< std::uint32_t > sortRows(std::vector< std::uint32_t > rows, RowOrder order)
Sorts rows by order in one call.
Definition sort.hpp:648
std::optional< double > parseReal(std::string_view text)
Parses a floating-point number, all of text.
Definition sort.hpp:153
TextCollator const & collatorOf(Services const &services)
The collator to use: the service's, or the default.
Definition sort.hpp:104
void buildKeys(RowSnapshot const &snapshot, std::size_t column, ColumnInfo const &info, Services const &services, std::size_t first, std::size_t last, KeyColumn &out)
Builds the keys of rows [first, last) of one column into out.
Definition sort.hpp:409
Exact rational number with a runtime decimal-precision tag.
Definition rational.hpp:375
A column's identity and kind.
One column's keys, built once and shared, immutable, between jobs.
Definition sort.hpp:178
void set(std::size_t row, Cell const &cell, ColumnInfo const &info, Services const &services)
Rebuilds the key of one row from cell.
Definition sort.hpp:253
std::vector< double > reals
Floating-point keys.
Definition sort.hpp:188
static KeyColumn forColumn(ColumnInfo const &info, Services const &services, std::size_t rows)
An empty key column for info, sized for rows rows.
Definition sort.hpp:209
ColumnKind kind
The column's kind.
Definition sort.hpp:180
std::vector< Cell > cells
Cells of a Custom column with a comparator.
Definition sort.hpp:196
std::vector< math::Rational > exact
Exact keys.
Definition sort.hpp:186
std::vector< std::int64_t > ints
Integer keys.
Definition sort.hpp:184
std::vector< CellState > states
The state of each row's cell.
Definition sort.hpp:182
std::vector< std::string > folded
Primary-strength folds, which text filters match.
Definition sort.hpp:192
std::vector< std::string > text
Collation keys.
Definition sort.hpp:190
bool usesComparator() const noexcept
Whether this column orders Custom cells through a comparator.
Definition sort.hpp:202
CellComparator comparator
The comparator of a Custom column, or empty.
Definition sort.hpp:198
std::vector< std::string > raw
The text as the source gave it, for case-sensitive filters.
Definition sort.hpp:194
One resolved key.
Definition sort.hpp:486
SortDirection dir
The key's direction.
Definition sort.hpp:490
KeyColumn const * keys
The column's keys.
Definition sort.hpp:488
The services an engine sorts and filters with. A null member is replaced by its default.
std::shared_ptr< DateParser const > dates
Date parsing; null means IsoDateParser.
std::shared_ptr< DateParser const > datesOrDefault() const
The date parser to use.
std::shared_ptr< TextCollator const > collatorOrDefault() const
The collator to use.
std::shared_ptr< TextCollator const > collator
Text order and matching; null means CodePointCollator.
std::map< std::string, CellComparator, std::less<> > comparators
Comparators for Custom columns, by the name ColumnInfo::comparator gives.
One key of a sort chain.
Definition sort.hpp:58
SortDirection dir
The key's direction.
Definition sort.hpp:62
bool operator==(SortKey const &other) const =default
Member-wise equality.
std::string column
The column's id.
Definition sort.hpp:60