29#include <core/async/StopToken.hpp>
41#include <system_error>
47namespace morph::table {
81using Deadline = std::chrono::steady_clock::time_point;
98 return stop.stop_requested() || (deadline !=
kNoDeadline && std::chrono::steady_clock::now() >= deadline);
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()) {
137 if (text.empty() || text.front() ==
'+' || std::isspace(
static_cast<unsigned char>(text.front())) != 0) {
140 std::istringstream stream{std::string{text}};
141 stream.imbue(std::locale::classic());
143 stream >> std::noskipws >> value;
144 if (stream.fail() || stream.peek() != std::char_traits<char>::eof() || std::isnan(value)) {
153[[nodiscard]]
inline std::optional<double>
parseReal(std::string_view text) {
154#if defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L
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)) {
184 std::vector<std::int64_t>
ints;
194 std::vector<std::string>
raw;
211 out.
kind = info.kind;
219 out.
ints.assign(rows, 0);
226 out.
reals.assign(rows, 0.0);
229 out.
text.resize(rows);
231 out.
raw.resize(rows);
234 if (
auto const found = services.
comparators.find(info.comparator);
235 found != services.
comparators.end() && found->second) {
237 out.
cells.resize(rows);
239 out.
text.resize(rows);
254 states[row] = read(row, cell, info, services);
259 if (std::holds_alternative<std::monostate>(cell)) {
262 auto const*
const textCell = std::get_if<std::string>(&cell);
263 if (textCell !=
nullptr && textCell->empty()) {
264 return CellState::Empty;
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);
281 return CellState::Valid;
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:
292 text[
row] = detail::collatorOf(services).sortKey(displayText(cell));
294 return CellState::Valid;
298 return CellState::Invalid;
301 [[nodiscard]]
CellState readInteger(std::size_t row, Cell
const& cell) {
302 if (
auto const* value = std::get_if<std::int64_t>(&cell)) {
304 return CellState::Valid;
306 if (
auto const* value = std::get_if<math::Rational>(&cell); value !=
nullptr && value->isInteger()) {
308 return CellState::Valid;
310 if (
auto const* value = std::get_if<std::string>(&cell)) {
311 if (
auto const parsed = detail::parseInteger(*value)) {
313 return CellState::Valid;
316 return CellState::Invalid;
319 [[nodiscard]]
CellState readExact(std::size_t row, Cell
const& cell) {
320 if (
auto const* value = std::get_if<math::Rational>(&cell)) {
322 return CellState::Valid;
324 if (
auto const* value = std::get_if<QuantityCell>(&cell)) {
327 auto const canonical = math::checkedMul(value->amount, value->toCanonical);
329 return CellState::Invalid;
332 return CellState::Valid;
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;
338 if (
auto const* value = std::get_if<std::string>(&cell)) {
341 return CellState::Valid;
344 return CellState::Invalid;
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)) {
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);
358 if (!value || std::isnan(*value)) {
359 return CellState::Invalid;
362 return CellState::Valid;
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)) {
369 return CellState::Valid;
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);
377 return CellState::Valid;
380 return CellState::Invalid;
383 [[nodiscard]]
CellState readBool(std::size_t row, Cell
const& cell) {
384 if (
auto const* value = std::get_if<bool>(&cell)) {
386 return CellState::Valid;
388 if (
auto const* value = std::get_if<std::int64_t>(&cell); value !=
nullptr && (*value == 0 || *value == 1)) {
390 return CellState::Valid;
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;
397 return CellState::Invalid;
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);
423 Sink sink{out, info, services};
424 snapshot.
readColumn(column, sink, first, last);
438 if (validA != validB) {
439 return validA ? -1 : 1;
444 auto const sign = [](
auto ordering) ->
int {
445 if (std::is_lt(ordering)) {
448 return std::is_gt(ordering) ? 1 : 0;
457 result = sign(keys.
ints[left] <=> keys.
ints[right]);
461 result = sign(keys.
exact[left] <=> keys.
exact[right]);
464 result = sign(std::strong_order(keys.
reals[left], keys.
reals[right]));
467 result = sign(keys.
text[left].compare(keys.
text[right]) <=> 0);
471 : sign(keys.
text[left].compare(keys.
text[right]) <=> 0);
498 explicit RowOrder(std::vector<Key> keys) : _keys{std::move(keys)} {}
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) {
516 [[nodiscard]]
bool empty() const noexcept {
return _keys.empty(); }
519 std::vector<Key> _keys;
531 static constexpr std::size_t
kRun = 512;
537 : _rows{std::move(rows)}, _order{std::move(order)} {
538 _buffer.resize(_rows.size());
545 bool run(
Deadline deadline, ::core::async::StopToken
const& stop) {
546 return sortRuns(deadline, stop) && mergePasses(deadline, stop);
551 [[nodiscard]]
bool done() const noexcept {
return _runStart >= _rows.size() && _width >= _rows.size(); }
555 [[nodiscard]] std::vector<std::uint32_t>
take() && {
return std::move(_rows); }
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;
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)) {
587 if (_width < count && detail::shouldYield(deadline, stop)) {
596 bool mergeOne(Deadline deadline, ::core::async::StopToken
const& stop) {
597 auto const count = _rows.size();
600 _leftEnd = std::min(_mergeAt + _width, count);
602 _rightEnd = std::min(_mergeAt + (2 * _width), count);
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)) {
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;
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;
625 _mergeAt = _rightEnd;
629 std::vector<std::uint32_t> _rows;
630 std::vector<std::uint32_t> _buffer;
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;
648[[nodiscard]]
inline std::vector<std::uint32_t>
sortRows(std::vector<std::uint32_t> rows,
RowOrder order) {
650 static_cast<void>(sort.
run(
kNoDeadline, ::core::async::StopToken{}));
651 return std::move(sort).
take();
660struct glz::meta<morph::table::SortDirection> {
663 static constexpr auto value = glz::enumerate(
"asc", Ascending,
"desc", Descending);
Handle< T > row()
The innermost row, {"row": ""}.
Handle< std::int64_t > count(AnyHandle const &list)
count: a list's length.
A bottom-up merge sort over row indices that can stop and resume.
static constexpr std::size_t kRun
Rows per initial run.
ChunkedMergeSort(std::vector< std::uint32_t > rows, RowOrder order)
Prepares to sort rows by order.
std::vector< std::uint32_t > take() &&
The sorted rows. Valid once done().
bool done() const noexcept
Whether the sort has finished.
bool run(Deadline deadline, ::core::async::StopToken const &stop)
Sorts until done, the deadline passes, or a stop is requested.
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.
bool empty() const noexcept
Whether this is source order.
RowOrder()=default
Source order.
RowOrder(std::vector< Key > keys)
An order over resolved keys, most significant first.
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...
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.
DateParser const & datesOf(Services const &services)
The date parser to use: the service's, or the default.
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.
std::vector< SortKey > SortChain
A multi-key sort, most significant key first. Empty means source order.
SortDirection
A sort key's direction.
@ Ascending
Smallest first.
@ Descending
Largest first. Invalid cells still come last.
std::optional< std::int64_t > parseInteger(std::string_view text)
Parses a whole decimal integer, all of text.
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 ...
CellState
Whether a cell has a key.
@ Valid
The cell has a key.
@ Invalid
The cell could not be read for its column's kind.
@ Empty
The cell is empty; it sorts with invalid cells.
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...
constexpr Deadline kNoDeadline
The deadline that never passes.
std::chrono::steady_clock::time_point Deadline
A deadline for one step of work; kNoDeadline runs to completion.
bool shouldYield(Deadline deadline, ::core::async::StopToken const &stop)
Whether a step should return now.
std::vector< std::uint32_t > sortRows(std::vector< std::uint32_t > rows, RowOrder order)
Sorts rows by order in one call.
std::optional< double > parseReal(std::string_view text)
Parses a floating-point number, all of text.
TextCollator const & collatorOf(Services const &services)
The collator to use: the service's, or the default.
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.
Exact rational number with a runtime decimal-precision tag.
A column's identity and kind.
One column's keys, built once and shared, immutable, between jobs.
void set(std::size_t row, Cell const &cell, ColumnInfo const &info, Services const &services)
Rebuilds the key of one row from cell.
std::vector< double > reals
Floating-point keys.
static KeyColumn forColumn(ColumnInfo const &info, Services const &services, std::size_t rows)
An empty key column for info, sized for rows rows.
ColumnKind kind
The column's kind.
std::vector< Cell > cells
Cells of a Custom column with a comparator.
std::vector< math::Rational > exact
Exact keys.
std::vector< std::int64_t > ints
Integer keys.
std::vector< CellState > states
The state of each row's cell.
std::vector< std::string > folded
Primary-strength folds, which text filters match.
std::vector< std::string > text
Collation keys.
bool usesComparator() const noexcept
Whether this column orders Custom cells through a comparator.
CellComparator comparator
The comparator of a Custom column, or empty.
std::vector< std::string > raw
The text as the source gave it, for case-sensitive filters.
SortDirection dir
The key's direction.
KeyColumn const * keys
The column's keys.
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.
SortDirection dir
The key's direction.
bool operator==(SortKey const &other) const =default
Member-wise equality.
std::string column
The column's id.