46namespace morph::table {
77 [[nodiscard]]
static constexpr std::string_view
schemaMarker() noexcept {
return "x-table"; }
82template <
typename Row>
88 std::optional<std::int64_t>
total;
131 auto const tooLong = [max](std::optional<std::string>
const& value) {
return value && value->size() > max; };
132 return tooLong(entry.
eq) || tooLong(entry.
ne) || tooLong(entry.
lt) || tooLong(entry.
le) || tooLong(entry.
gt) ||
141[[nodiscard]]
inline bool anyTooLong(std::span<FilterEntry const> entries, std::size_t max) {
150[[nodiscard]]
inline std::optional<TableError>
checkColumn(std::string
const& columnId,
151 std::span<ColumnInfo const> columns,
152 std::function<
bool(std::string_view)>
const& mayRead) {
156 if (mayRead && !mayRead(columnId)) {
159 .message =
"column " + columnId +
" is not readable"};
171 std::span<ColumnInfo const> columns,
173 std::function<
bool(std::string_view)>
const& mayRead) {
175 return limitError({},
"too many filtered columns");
177 for (
auto const& [columnId, filter] : filters.
columns) {
178 if (
auto error =
checkColumn(columnId, columns, mayRead)) {
181 if (filter.include.size() + filter.exclude.size() > limits.
maxEntries) {
182 return limitError(columnId,
"too many filter entries");
185 return limitError(columnId,
"filter value too long");
189 return limitError({},
"too many filter groups");
191 for (
auto const& group : filters.
groups) {
193 return limitError({},
"filter group too large");
195 for (
auto const& columnId : group.columns) {
196 if (
auto error =
checkColumn(columnId, columns, mayRead)) {
201 return limitError({},
"filter value too long");
220[[nodiscard]]
inline std::expected<TableQuery, TableError>
validate(
222 std::function<bool(std::string_view)>
const& mayRead = {}) {
224 return std::unexpected(detail::limitError({},
"page.offset out of range"));
230 if (query.
sort.size() > limits.maxSortKeys) {
233 for (
auto const& key : query.
sort) {
235 return std::unexpected(std::move(*error));
239 return std::unexpected(std::move(*error));
252 out.reserve(text.size());
253 for (
char const character : text) {
254 if (character ==
'%' || character ==
'_' || character == escape) {
255 out.push_back(escape);
257 out.push_back(character);
270 std::function<bool(std::string_view)>
mayRead;
291 auto const columns = source.
columns();
292 auto checked = validate(query, columns, options.limits, options.mayRead);
294 return std::unexpected(std::move(checked.error()));
299 auto services = options.services.forTask();
300 auto filter = compileFilter(checked->filters, columns, services);
302 return std::unexpected(std::move(filter.error()));
306 input.columns.assign(columns.begin(), columns.end());
307 input.services = std::move(services);
308 input.keys.resize(columns.size());
309 for (
auto const& key : checked->sort) {
311 input.needed.push_back(*column);
314 for (
auto const column :
filter->columnsUsed()) {
315 input.needed.push_back(column);
317 std::ranges::sort(input.needed);
318 auto const [first, last] = std::ranges::unique(input.needed);
319 input.needed.erase(first, last);
320 input.sort = checked->sort;
321 input.filter = std::make_shared<CompiledFilter const>(std::move(*filter));
322 input.previousSnapshot = input.snapshot;
324 static_cast<void>(job.step(
kNoDeadline, ::core::async::StopToken{}));
332 out.
offset = checked->page.offset;
333 auto const count =
static_cast<std::int64_t
>(view.size());
334 if (count <= options.limits.totalCap) {
337 auto const begin = std::min(checked->page.offset, count);
339 auto const end = checked->page.limit >=
count - begin ?
count : begin + checked->page.limit;
340 for (
auto i = begin; i < end; ++i) {
341 out.rows.push_back(view[
static_cast<std::size_t
>(i)]);
364 std::is_member_object_pointer_v<std::remove_cvref_t<
decltype(glz::meta<V>::value)>>;
369using WrappedMember = std::remove_cvref_t<decltype(std::declval<V const&>().*(glz::meta<V>::value))>;
376 using V = std::remove_cvref_t<T>;
379 }
else if constexpr (std::is_same_v<V, bool>) {
381 }
else if constexpr (std::integral<V>) {
383 }
else if constexpr (std::floating_point<V>) {
385 }
else if constexpr (std::is_same_v<V, math::Rational>) {
389 }
else if constexpr (std::is_same_v<V, time::DateTime> || std::is_same_v<V, time::Timestamp>) {
406 if constexpr (std::is_unsigned_v<T> &&
sizeof(T) >=
sizeof(std::int64_t)) {
407 if (value >
static_cast<T
>(std::numeric_limits<std::int64_t>::max())) {
408 return Cell{std::to_string(value)};
411 return Cell{
static_cast<std::int64_t
>(value)};
422 if (glz::write_json(value, text) || text ==
"null") {
425 if (text.size() >= 2 && text.front() ==
'"' && text.back() ==
'"') {
427 if (!glz::read_json(decoded, text)) {
428 return Cell{std::move(decoded)};
431 return Cell{std::move(text)};
440 using V = std::remove_cvref_t<T>;
443 }
else if constexpr (std::is_same_v<V, bool> || std::is_same_v<V, std::string> ||
444 std::is_same_v<V, math::Rational>) {
446 }
else if constexpr (std::integral<V>) {
448 }
else if constexpr (std::floating_point<V>) {
449 return Cell{
static_cast<double>(value)};
451 return value.value() ?
Cell{*value.value()} :
Cell{};
452 }
else if constexpr (std::is_same_v<V, time::DateTime>) {
453 return Cell{std::chrono::floor<std::chrono::seconds>(value.value).time_since_epoch().count()};
454 }
else if constexpr (std::is_same_v<V, time::Timestamp>) {
455 return value.value ?
toCell(*value.value) :
Cell{};
457 return toCell(value.*(glz::meta<V>::value));
465template <
typename Row>
471template <
typename Row>
473 constexpr auto count = glz::reflect<Row>::size;
474 return [&]<std::size_t... I>(std::index_sequence<I...>) {
475 return std::vector<CellReader<Row>>{
477 }(std::make_index_sequence<count>{});
483template <
typename Row>
485 constexpr auto count = glz::reflect<Row>::size;
486 std::vector<ColumnInfo> out;
487 [&]<std::size_t... I>(std::index_sequence<I...>) {
489 ColumnInfo{.id = std::string{glz::reflect<Row>::keys[I]},
494 }(std::make_index_sequence<count>{});
502template <
typename Row>
507template <
typename Row>
516 : _rows{std::move(rows)}, _key{std::move(key)}, _readers{std::move(readers)} {}
520 [[nodiscard]] std::size_t
rowCount()
const override {
return _rows->size(); }
525 [[nodiscard]]
RowId rowId(std::size_t row)
const override {
return _key((*_rows)[row]); }
531 [[nodiscard]]
Cell cell(std::size_t row, std::size_t column)
const override {
532 return (*_readers)[column]((*_rows)[row]);
541 constexpr std::size_t kRun = 256;
542 std::vector<Cell> run;
544 auto const reader = (*_readers)[column];
545 while (first < last) {
546 auto const end = std::min(first + kRun, last);
548 for (
auto row = first; row < end; ++row) {
549 run.push_back(reader((*_rows)[row]));
551 sink.
cells(first, run);
557 std::shared_ptr<std::vector<Row>
const> _rows;
559 std::shared_ptr<std::vector<detail::CellReader<Row>>
const> _readers;
574template <
typename Row>
585 std::map<std::string,
ColumnKind, std::less<>>
const& kinds = {},
586 std::map<std::string, std::string, std::less<>>
const& comparators = {})
587 : _columns{detail::reflectedColumns<Row>()},
588 _readers{std::make_shared<std::vector<detail::CellReader<Row>> const>(detail::cellReaders<Row>())},
589 _key{std::move(key)},
590 _snapshot{std::make_shared<RowsSnapshot<Row>>(std::move(rows), _key, _readers)} {
591 for (
auto& column : _columns) {
592 if (
auto const found = kinds.find(column.
id); found != kinds.end()) {
593 column.kind = found->second;
595 if (
auto const found = comparators.find(column.
id); found != comparators.end()) {
596 column.kind = ColumnKind::Custom;
597 column.comparator = found->second;
604 [[nodiscard]] std::span<ColumnInfo const>
columns()
const override {
return _columns; }
608 [[nodiscard]] std::shared_ptr<RowSnapshot const>
snapshot()
const override {
return _snapshot; }
620 void setRows(std::shared_ptr<std::vector<Row>
const> rows) {
621 _snapshot = std::make_shared<RowsSnapshot<Row>>(std::move(rows), _key, _readers);
626 std::vector<ColumnInfo> _columns;
627 std::shared_ptr<std::vector<detail::CellReader<Row>>
const> _readers;
629 std::shared_ptr<RowsSnapshot<Row>
const> _snapshot;
643template <
typename Row>
646 std::map<std::string, ColumnKind, std::less<>>
const& kinds = {},
647 std::map<std::string, std::string, std::less<>>
const& comparators = {}) {
649 std::shared_ptr<std::vector<Row>
const>
const borrowed{std::shared_ptr<void>{}, &rows};
650 RowsSource<Row> const source{borrowed, std::move(key), kinds, comparators};
651 auto page =
apply(source, query, options);
653 return std::unexpected(std::move(page.error()));
656 out.
total = page->total;
657 out.offset = page->offset;
658 out.rows.reserve(page->rows.size());
659 for (
auto const row : page->rows) {
660 out.rows.push_back(rows[row]);
695template <
typename Row>
702 explicit PageWindow(std::int64_t pageSize = 200, std::size_t maxPages = 8)
703 : _pageSize{std::max<std::int64_t>(pageSize, 1)}, _maxPages{std::max<std::size_t>(maxPages, 1)} {}
714 [[nodiscard]] std::vector<PageFetch>
visible(std::int64_t first, std::int64_t count) {
715 _visibleFirst = std::max<std::int64_t>(first, 0);
716 _visibleCount = std::max<std::int64_t>(count, 0);
717 auto const firstPage = std::max<std::int64_t>((_visibleFirst / _pageSize) - 1, 0);
718 auto const lastPage = ((_visibleFirst + std::max<std::int64_t>(_visibleCount, 1) - 1) / _pageSize) + 1;
719 std::vector<PageFetch> out;
720 for (
auto page = firstPage; page <= lastPage; ++page) {
721 if (_total && page * _pageSize >= *_total) {
724 if (_complete.contains(page) || std::ranges::find(_inFlight, page) != _inFlight.end()) {
727 auto const held = _pages.find(page);
728 auto const have = held == _pages.end() ? std::int64_t{0} :
static_cast<std::int64_t
>(held->second.size());
729 _inFlight.push_back(page);
731 PageFetch{.request =
PageRequest{.offset = (page * _pageSize) + have, .limit = _pageSize - have},
743 if (fetch.
query != _query) {
747 auto const flight = std::ranges::find(_inFlight, index);
748 if (flight == _inFlight.end()) {
751 _inFlight.erase(flight);
756 auto& rows = _pages[index];
757 bool const replyEmpty = page.
rows.empty();
758 if (fetch.
request.
offset == (index * _pageSize) +
static_cast<std::int64_t
>(rows.size())) {
759 auto const room =
static_cast<std::size_t
>(_pageSize) - rows.size();
760 auto const take = std::min(room, page.
rows.size());
761 rows.insert(rows.end(), std::make_move_iterator(page.
rows.begin()),
762 std::make_move_iterator(page.
rows.begin() +
static_cast<std::ptrdiff_t
>(take)));
764 auto const end = (index * _pageSize) +
static_cast<std::int64_t
>(rows.size());
765 if (std::cmp_greater_equal(rows.size(), _pageSize) || (_total && end >= *_total) || replyEmpty) {
766 _complete.insert(index);
777 if (fetch.
query != _query) {
781 _error = std::move(
error);
798 [[nodiscard]] std::uint64_t
query() const noexcept {
return _query; }
803 [[nodiscard]] Row
const*
row(std::int64_t index)
const {
807 auto const found = _pages.find(index / _pageSize);
808 if (found == _pages.end()) {
811 auto const offset =
static_cast<std::size_t
>(index % _pageSize);
812 return offset < found->second.size() ? &found->second[offset] :
nullptr;
817 [[nodiscard]] std::optional<std::int64_t>
total() const noexcept {
return _total; }
821 [[nodiscard]] std::optional<TableError>
const&
error() const noexcept {
return _error; }
825 [[nodiscard]] std::size_t
pagesHeld() const noexcept {
return _pages.size(); }
829 [[nodiscard]]
bool pending() const noexcept {
return !_inFlight.empty(); }
833 auto const center = (_visibleFirst + (_visibleCount / 2)) / _pageSize;
834 while (_pages.size() > _maxPages) {
835 auto farthest = _pages.begin();
836 for (
auto at = _pages.begin(); at != _pages.end(); ++at) {
837 if (std::abs(at->first - center) > std::abs(farthest->first - center)) {
841 _complete.erase(farthest->first);
842 _pages.erase(farthest);
846 std::int64_t _pageSize;
847 std::size_t _maxPages;
848 std::uint64_t _query = 0;
849 std::map<std::int64_t, std::vector<Row>> _pages;
850 std::set<std::int64_t> _complete;
851 std::vector<std::int64_t> _inFlight;
852 std::optional<std::int64_t> _total;
853 std::optional<TableError> _error;
854 std::int64_t _visibleFirst = 0;
855 std::int64_t _visibleCount = 0;
Handle< std::int64_t > count(AnyHandle const &list)
count: a list's length.
AnyHandle filter(AnyHandle const &source, std::string const &name, std::type_identity_t< std::function< Handle< bool >(Handle< E > const &)> > const &where)
A filter over a list, {"filter": e, "as": "x", "where": e}.
Receives a DataSource's change notifications, on the owner thread.
Receives a run of one column's cells.
virtual void cells(std::size_t firstRow, std::span< Cell const > cells)=0
Receives consecutive cells of one column.
The rows of a table, as the engine reads them.
virtual std::span< ColumnInfo const > columns() const =0
The table's columns.
virtual std::shared_ptr< RowSnapshot const > snapshot() const =0
The current rows as an immutable snapshot. Taking one is cheap.
bool accept(PageFetch const &fetch, Page< Row > page)
Stores a reply.
void reset()
Drops every page, request and error and starts a new query: the sort or filter changed.
std::optional< std::int64_t > total() const noexcept
The view's total rows, when a reply of this query reported it.
std::vector< PageFetch > visible(std::int64_t first, std::int64_t count)
Declares the visible rows; returns the fetches to send.
Row const * row(std::int64_t index) const
The row at a view row, when it is held.
std::uint64_t query() const noexcept
The current query, which reset advances.
std::size_t pagesHeld() const noexcept
Pages held, complete or in part.
PageWindow(std::int64_t pageSize=200, std::size_t maxPages=8)
An empty window.
bool fail(PageFetch const &fetch, TableError error)
Records a failed request; the error is shown until a reply arrives.
std::optional< TableError > const & error() const noexcept
The last error, until a reply clears it.
bool pending() const noexcept
Whether any request is in flight.
RowId rowId(std::size_t row) const override
A row's key.
Cell cell(std::size_t row, std::size_t column) const override
One cell.
void readColumn(std::size_t column, ColumnSink &sink, std::size_t first, std::size_t last) const override
Hands rows [first, last) of column to sink in runs.
RowsSnapshot(std::shared_ptr< std::vector< Row > const > rows, RowKey< Row > key, std::shared_ptr< std::vector< detail::CellReader< Row > > const > readers)
Wraps rows.
std::size_t rowCount() const override
The number of rows.
A DataSource over typed rows: one column per key glaze writes, its kind inferred from the value's typ...
std::span< ColumnInfo const > columns() const override
The columns.
void subscribe(ChangeListener &listener) override
Registers listener.
void unsubscribe(ChangeListener &listener) override
Removes listener; safe from inside a notification.
RowsSource(std::shared_ptr< std::vector< Row > const > rows, RowKey< Row > key, std::map< std::string, ColumnKind, std::less<> > const &kinds={}, std::map< std::string, std::string, std::less<> > const &comparators={})
Builds the source.
std::shared_ptr< RowSnapshot const > snapshot() const override
The current rows; a pointer copy.
void setRows(std::shared_ptr< std::vector< Row > const > rows)
Replaces the rows and notifies Reset.
A source's listeners, notified so that a listener may unsubscribe itself or another,...
One recomputation of a view, runnable in steps.
JobResult takeResult()
Moves the result out. Valid once done(); the input stays readable.
Node id(Element const &element) const
The element id, by which relations read its values.
A type glaze writes as one of its own members, as morph's Ranged, Choice, Multiline and Tagged are: i...
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.
@ Reset
Everything may have changed, the columns included.
ColumnKind
What a column holds, which decides how its keys are built and compared.
@ Quantity
An exact Rational in the column's canonical unit.
@ DateTime
UTC seconds since 1970-01-01T00:00:00Z.
@ 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.
@ LimitExceeded
A TableQuery over one of the server's bounds.
@ ServiceFailed
An injected service (collator, date parser, comparator) threw.
@ ColumnNotReadable
A sort or filter on a column the principal may not read.
std::variant< std::int64_t, std::string > RowId
A row's identity: the table's key, stable across reloads.
UTC timestamps for morph actions.
The table engine: sort, filter and view mapping, on and off the owner.
Filter specs as data, compiled once and evaluated column at a time.
TableError unknownColumn(std::string const &columnId)
An UnknownColumn error.
std::optional< std::size_t > findColumn(std::span< ColumnInfo const > columns, std::string_view columnId)
The index of the column with id columnId.
Unit-tagged, optionally-empty exact values with a derivation trail.
constexpr bool isQuantity
true when T (cvref-stripped) is a morph::units::Quantity.
std::string escapeLikePattern(std::string_view text, char escape='\\')
Escapes %, _ and the escape character for a SQL LIKE pattern, so a database-backed server keeps them ...
std::remove_cvref_t< decltype(std::declval< V const & >().*(glz::meta< V >::value))> WrappedMember
The member a MemberWrapper is written as.
std::vector< ColumnInfo > reflectedColumns()
One column per key glaze writes, its kind from the value's type.
std::expected< TableQuery, TableError > validate(TableQuery query, std::span< ColumnInfo const > columns, QueryLimits const &limits={}, std::function< bool(std::string_view)> const &mayRead={})
Checks query against the table's columns and limits.
TableError limitError(std::string column, std::string message)
A LimitExceeded error.
Cell writtenCell(T const &value)
A member as the text glaze writes for it: an enum by its glaze name, a JSON string decoded,...
std::function< RowId(Row const &)> RowKey
A key projection: the RowId of a typed row.
std::optional< TableError > checkFilters(FilterSpec const &filters, std::span< ColumnInfo const > columns, QueryLimits const &limits, std::function< bool(std::string_view)> const &mayRead)
Checks a filter's columns, entry counts and value lengths.
Cell toCell(T const &value)
A member value as a cell.
std::optional< TableError > checkColumn(std::string const &columnId, std::span< ColumnInfo const > columns, std::function< bool(std::string_view)> const &mayRead)
Checks that a column exists and the principal may read it.
Cell(*)(Row const &) CellReader
Reads one column's cell from a row.
bool entryTooLong(FilterEntry const &entry, std::size_t max)
Whether any operand of an entry is longer than max bytes.
bool anyTooLong(std::span< FilterEntry const > entries, std::size_t max)
Whether any of entries has an operand longer than max bytes.
constexpr ColumnKind kindOf()
The column kind a member type gives.
Cell integerCell(T value)
An integer member as a cell.
std::expected< PageRows, TableError > apply(DataSource const &source, TableQuery const &query, ApplyOptions const &options={})
Runs query over source in the calling thread: the reference implementation of server mode.
std::vector< CellReader< Row > > cellReaders()
One cell reader per key glaze writes, in key order.
The value glaze reads and writes under a reflected key.
constexpr decltype(auto) reflectedMember(T &object)
The value glaze reads and writes under glz::reflect<T>::keys[I].
Typed sort keys and the chunked, stoppable merge sort over them.
constexpr Deadline kNoDeadline
The deadline that never passes.
Services services
The server's collator, date parser and comparators. Its collator decides the order of text in the pag...
QueryLimits limits
The bounds of validate.
std::function< bool(std::string_view)> mayRead
Whether the principal may read a column; empty allows every column.
A column's identity and kind.
std::string comparator
For a Custom column, the name of its comparator in Services::comparators.
One filter condition. Exactly one operator member is set.
std::optional< std::string > contains
Text contains. Text columns.
std::optional< std::string > ge
Greater than or equal to. Numeric, date and date-time columns.
std::optional< std::string > ne
Not equal to.
std::optional< std::array< std::string, 2 > > between
Between two values, both inclusive. Numeric, date and date-time columns.
std::optional< std::string > eq
Equal to (text: at primary strength unless caseSensitive).
std::optional< std::string > endsWith
Text ends with. Text columns.
std::optional< std::string > gt
Greater than. Numeric, date and date-time columns.
std::optional< std::string > lt
Less than. Numeric, date and date-time columns.
std::optional< std::string > le
Less than or equal to. Numeric, date and date-time columns.
std::optional< std::string > startsWith
Text starts with. Text columns.
std::map< std::string, ColumnFilter > columns
Per-column filters, by column id.
std::vector< GroupFilter > groups
Multi-column groups.
A request a PageWindow asks the client to send, tagged with the query it belongs to.
std::uint64_t query
The window's query when the fetch was made (PageWindow::query).
PageRequest request
The rows to request.
bool operator==(PageFetch const &other) const =default
Member-wise equality.
One page of a list action's reply.
std::int64_t offset
The view row of rows[0].
std::optional< std::int64_t > total
Rows in the whole filtered view; absent when the server did not count them or the count is above its ...
std::vector< Row > rows
The rows, in view order.
The bounds a server applies to every TableQuery, an untrusted body.
std::size_t maxGroups
Groups.
std::size_t maxSortKeys
Sort keys.
std::size_t maxGroupColumns
Columns of one group.
std::size_t maxTextLength
Bytes of one filter value.
std::int64_t maxOffset
page.offset above this is refused.
std::int64_t totalCap
A total above this is not reported.
std::size_t maxFilterColumns
Filtered columns.
std::size_t maxEntries
include plus exclude entries of one column or group.
std::int64_t maxLimit
page.limit above this is clamped to it.
The services an engine sorts and filters with. A null member is replaced by its default.
The error every fallible table call returns.
A table's sort, filters and page, as a list action's body carries them.
static constexpr std::string_view schemaMarker() noexcept
The schema keyword that marks a member of this type.
std::vector< SortKey > sort
The sort chain.
FilterSpec filters
The filter.
PageRequest page
The page.
true for std::optional<T>.
std::vector< std::uint32_t > view
The sorted, filtered view.