/* * Copyright (C) 2024-present ScyllaDB */ /* * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 */ #pragma once #include "schema/column_computation.hh" #include "mutation/atomic_cell.hh" #include "mutation/timestamp.hh" #include class row_marker; // In a basic column_computation defined in column_computation.hh, the // compute_value() method is only based on the partition key, and it must // return a value. That API has very limited applications - basically the // only thing we can implement with it is token_column_computation which // we used to create the token column in secondary indexes. // The regular_column_transformation base class here is more powerful, but // still is not a completely general computation: Its compute_value() virtual // method can transform the value read from a single cell of a regular column // into a new cell stored in a structure regular_column_transformation::result. // // In more details, the assumptions of regular_column_transformation is: // 1. compute_value() computes the value based on a *single* column in a // row passed to compute_value(). // This assumption means that the value or deletion of the value always // has a single known timestamp (and the value can't be half-missing) // and single TTL information. That would not have been possible if we // allowed the computation to depend on multiple columns. // 2. compute_value() computes the value based on a *regular* column in the // base table. This means that an update can modify this value (unlike a // base-table key column that can't change in an update), so the view // update code needs to compute the value before and after the update, // and potentially delete and create view rows. // 3. compute_value() returns a column_computation::result which includes // a value and its liveness information (timestamp and ttl/expiry) or // is missing a value. class regular_column_transformation : public column_computation { public: struct result { // We can use "bytes" instead of "managed_bytes" here because we know // that a column_computation is only used for generating a key value, // and that is limited to 64K. This limitation is enforced below - // we never linearize a cell's value if its size is more than 64K. std::optional _value; // _ttl and _expiry are only defined if _value is set. // The default values below are used when the source cell does not // expire, and are the same values that row_marker uses for a non- // expiring marker. This is useful when creating a row_marker from // get_ttl() and get_expiry(). gc_clock::duration _ttl { 0 }; gc_clock::time_point _expiry { gc_clock::duration(0) }; // _ts may be set even if _value is missing, which can remember the // timestamp of a tombstone. Note that the current view-update code // that uses this class doesn't use _ts when _value is missing. api::timestamp_type _ts = api::missing_timestamp; api::timestamp_type get_ts() const { return _ts; } bool has_value() const { return _value.has_value(); } // Should only be called if has_value() is true: const bytes& get_value() const { return *_value; } gc_clock::duration get_ttl() const { return _ttl; } gc_clock::time_point get_expiry() const { return _expiry; } // A missing computation result result() { } // Construct a computation result by copying a given atomic_cell - // including its value, timestamp, and ttl - or deletion timestamp. // The second parameter is an optional transformation function f - // taking a bytes and returning an optional - that transforms // the value of the cell but keeps its other liveness information. // If f returns a nullopt, it causes the view row should be deleted. template requires std::invocable && std::convertible_to, std::optional> result(atomic_cell_view cell, Func f = {}) { _ts = cell.timestamp(); if (cell.is_live()) { // If the cell is larger than what a key can hold (64KB), // return a missing value. This lets us skip this item during // view building and avoid hanging the view build as described // in #8627. But it doesn't prevent later inserting such a item // to the base table, nor does it implement front-end specific // limits (such as Alternator's 1K or 2K limits - see #10347). // Those stricter limits should be validated in the base-table // write code, not here - deep inside the view update code. // Note also we assume that f() doesn't grow the value further. if (cell.value().size() >= 65536) { return; } _value = f(to_bytes(cell.value())); if (_value) { if (cell.is_live_and_has_ttl()) { _ttl = cell.ttl(); _expiry = cell.expiry(); } } } } }; virtual ~regular_column_transformation() = default; virtual result compute_value( const schema& schema, const partition_key& key, const db::view::clustering_or_static_row& row) const = 0; };