use reifydb_codec::key::encoded::EncodedKey;
use reifydb_value::{
error::Diagnostic,
fragment::Fragment,
value::{duration::Duration, value_type::ValueType},
};
use crate::interface::catalog::flow::OperatorId;
pub fn flow_error(message: String) -> Diagnostic {
Diagnostic {
code: "FLOW_001".to_string(),
rql: None,
message: format!("Flow processing error: {}", message),
column: None,
fragment: Fragment::None,
label: None,
help: Some("Check view flow configuration".to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_remote_source_unsupported() -> Diagnostic {
Diagnostic {
code: "FLOW_007".to_string(),
rql: None,
message: "Cannot create flow for remote source".to_string(),
column: None,
fragment: Fragment::None,
label: None,
help: Some("Remote tables do not support local flow graphs. Use remote subscription proxying instead."
.to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_source_required() -> Diagnostic {
Diagnostic {
code: "FLOW_008".to_string(),
rql: None,
message: "Flow requires at least one source".to_string(),
column: None,
fragment: Fragment::None,
label: None,
help: Some("A view flow must read from a table, view, ring buffer, or series. \
Inline data (FROM [...]) cannot be used as the source for a view."
.to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_window_requires_a_timed_source() -> Diagnostic {
Diagnostic {
code: "FLOW_014".to_string(),
rql: None,
message: "a windowed view requires a source that declares a time domain".to_string(),
column: None,
fragment: Fragment::None,
label: None,
help: Some("declare `time: event(<column>)` or `time: processing` on the source, \
or drop the window from the view"
.to_string()),
notes: vec![
"an undeclared source is `time: none`, whose rows carry no #time".to_string(),
"a window buckets and seals on #time, so it can never fire over rows that have none"
.to_string(),
],
cause: None,
operator_chain: None,
}
}
pub fn flow_sort_must_be_terminal() -> Diagnostic {
Diagnostic {
code: "FLOW_012".to_string(),
rql: None,
message: "sort is only supported as the final operator in a view".to_string(),
column: None,
fragment: Fragment::None,
label: None,
help: Some(
"Move the sort to the end of the pipeline so its output is not consumed by another operator. \
A view may sort its result, but cannot apply further operators after a sort."
.to_string(),
),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_unsupported_aggregate_expression(output: &str) -> Diagnostic {
Diagnostic {
code: "FLOW_013".to_string(),
rql: None,
message: format!("aggregate output '{}' is not a supported aggregate expression in a view", output),
column: None,
fragment: Fragment::None,
label: None,
help: Some("Window and aggregate views support math::count, math::sum, math::avg, math::min and \
math::max over a column or scalar expression, and stats::digest over a column or scalar \
expression with a literal accuracy (or over a digest without one), and \
stats::approx_percentile with a literal p over the same inputs, optionally combined with \
arithmetic (for example math::max(x) - math::min(x)). Every output must reduce to such an \
aggregate."
.to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_window_span_unavailable(output: &str, kind: &str) -> Diagnostic {
Diagnostic {
code: "FLOW_015".to_string(),
rql: None,
message: format!(
"aggregate output '{}' needs a window boundary, but a {} window has none",
output, kind
),
column: None,
fragment: Fragment::None,
label: None,
help: Some("window::start, window::end and window::duration are available on tumbling, sliding \
and session windows sized by a duration. A rolling window and a window sized by a row \
count have no boundary; window::last works on every window kind."
.to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
fn flow_diagnostic(code: &str, message: String, help: &str) -> Diagnostic {
Diagnostic {
code: code.to_string(),
rql: None,
message,
column: None,
fragment: Fragment::None,
label: None,
help: Some(help.to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_state_encode_failed(state: &str, cause: String) -> Diagnostic {
flow_diagnostic(
"FLOW_021",
format!("failed to serialize flow operator state '{}': {}", state, cause),
"An operator failed to encode its persistent state. This usually indicates a bug in the operator's \
state serialization, not user input.",
)
}
pub fn flow_state_decode_failed(state: &str, cause: String) -> Diagnostic {
flow_diagnostic(
"FLOW_022",
format!("failed to deserialize flow operator state '{}': {}", state, cause),
"An operator failed to decode its persistent state. This may indicate on-disk state corruption or a \
state-format change between versions.",
)
}
pub fn flow_unsupported_operator(kind: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_023",
format!("operator kind '{}' is not supported in persistent flows", kind),
"This operator kind cannot appear in a persistent view flow. Rewrite the view without it.",
)
}
pub fn flow_operator_input_arity(operator: &str, expected: &str, found: usize) -> Diagnostic {
flow_diagnostic(
"FLOW_024",
format!("operator '{}' requires {} inputs, but the DAG provided {}", operator, expected, found),
"The compiled flow DAG has the wrong number of input edges for this operator. This indicates a flow \
compiler or catalog inconsistency.",
)
}
pub fn flow_append_lane_mismatch(column: &str, expected: &str, found: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_089",
format!(
"append lanes disagree on column '{}': the first lane is {}, the second is {}",
column, expected, found
),
"Every lane of an append must carry a column with the same type and dictionary, otherwise rows of one lane \
would land in a column typed by the other.",
)
}
pub fn flow_state_key_unframed(operator: OperatorId, key: &EncodedKey) -> Diagnostic {
let mut diagnostic = flow_diagnostic(
"FLOW_090",
format!("operator {} holds a state key with no valid frame", operator.0),
"Every stored operator state key must carry a group, a known keyspace and its suffix, otherwise a range \
read would skip the row and the reaper would never free it.",
);
diagnostic.notes = vec![format!("key: {}", hex(key))];
diagnostic
}
pub fn flow_pending_state_key_undecodable(key: &EncodedKey) -> Diagnostic {
let mut diagnostic = flow_diagnostic(
"FLOW_091",
"a pending operator state write carries a key with no operator".to_string(),
"Every operator state write must name its operator, otherwise the commit routes it to neither the operator \
store nor the row store and the write is lost.",
);
diagnostic.notes = vec![format!("key: {}", hex(key))];
diagnostic
}
fn hex(key: &EncodedKey) -> String {
key.as_slice().iter().map(|byte| format!("{:02x}", byte)).collect()
}
pub fn flow_parent_operator_not_found(input: String) -> Diagnostic {
flow_diagnostic(
"FLOW_025",
format!("parent operator not found while wiring operator input: {}", input),
"An operator references a parent operator that has not been registered. The flow DAG is incomplete \
or operators were registered out of order.",
)
}
pub fn flow_unknown_operator(operator: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_026",
format!("unknown flow operator '{}'", operator),
"The flow references an operator that is not registered in this build. Check for a missing extern \
operator or a typo in the operator name.",
)
}
pub fn flow_extern_unsupported_on_wasm() -> Diagnostic {
flow_diagnostic(
"FLOW_027",
"extern operators are not supported on the wasm target".to_string(),
"Extern operators cannot be loaded in a wasm runtime. Use only built-in operators.",
)
}
pub fn flow_missing_input_edge() -> Diagnostic {
flow_diagnostic(
"FLOW_028",
"operator is missing a required input edge; the flow DAG is incomplete".to_string(),
"The compiled flow DAG is missing an edge that a operator requires. This indicates a flow compiler bug.",
)
}
pub fn flow_unknown_diff_origin(operator: &str, origin: Option<String>) -> Diagnostic {
let message = match origin {
Some(o) => format!("{} operator received a diff from an unknown operator: {}", operator, o),
None => format!("{} operator received a diff from an unknown operator", operator),
};
flow_diagnostic(
"FLOW_029",
message,
"An operator received change data tagged with an origin it does not have wired as an input. This \
indicates a flow routing or DAG inconsistency.",
)
}
pub fn extern_abi_tag_mismatch(plugin: u32, host: u32) -> Diagnostic {
flow_diagnostic(
"FLOW_031",
format!("extern operator ABI tag mismatch: plugin reports {:#06x}, host expects {:#06x}", plugin, host),
"The extern operator library was built against a different ABI than this host. Rebuild the extern \
operators against the current version.",
)
}
pub fn extern_library_not_loaded(path: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_032",
format!("extern operator library not loaded: {}", path),
"The extern operator shared library could not be loaded. Check that the .so exists and is readable.",
)
}
pub fn extern_symbol_not_found(symbol: &str, cause: String) -> Diagnostic {
flow_diagnostic(
"FLOW_033",
format!("extern operator symbol '{}' not found: {}", symbol, cause),
"The extern operator library is missing an expected symbol. It may be built against a different ABI \
or be the wrong library.",
)
}
pub fn extern_operator_not_found(operator: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_034",
format!("extern operator '{}' not found", operator),
"No loaded extern library provides this operator. Check the operators directory and the operator name.",
)
}
pub fn extern_create_failed(cause: String) -> Diagnostic {
flow_diagnostic(
"FLOW_035",
format!("failed to create extern operator: {}", cause),
"The extern operator's create function returned an error. See the underlying cause.",
)
}
pub fn extern_column_type_unsupported(column: &str, ty: ValueType) -> Diagnostic {
flow_diagnostic(
"EXTERN_001",
format!("column '{}' of type {} cannot be passed to an extension", column, ty),
"Extensions receive scalar columns only. Read what the extension needs from the column first, e.g., \
'stats::approx_percentile(d, 0.99)', and pass that instead.",
)
}
pub fn flow_sink_missing_system_column(column: &str, row_idx: usize) -> Diagnostic {
flow_diagnostic(
"FLOW_036",
format!("row at index {} is missing the '{}' system column", row_idx, column),
"A view sink row is missing a required system timestamp column. This indicates an encoding bug \
upstream of the sink.",
)
}
pub fn flow_sink_dictionary_not_found(dictionary_id: String, column: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_037",
format!("dictionary {} not found for view column '{}'", dictionary_id, column),
"A dictionary-encoded view column references a dictionary that no longer exists in the catalog.",
)
}
pub fn flow_sink_dictionary_entry_not_found(dictionary_id: String, column: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_088",
format!("dictionary {} has no entry for a value removed from view column '{}'", dictionary_id, column),
"A view sink removed a row whose dictionary-encoded value was never interned, so the stored row cannot exist.",
)
}
pub fn flow_sink_not_a_source_family(family: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_047",
format!("a view sink cannot encode a row of the {} family", family),
"Only the table, series and ring buffer families reserve the created_at, updated_at and #time \
slots a view sink stamps. This indicates the sink was built over the wrong shape.",
)
}
pub fn flow_sink_missing_series_key(view: &str, column: &str, row_idx: usize) -> Diagnostic {
flow_diagnostic(
"FLOW_050",
format!("row at index {} of view '{}' has no series key in column '{}'", row_idx, view, column),
"A series view row key is built from the view's own key column, so the column must be present \
and hold a value convertible to an unsigned integer. A none value or an unsupported type here \
would collapse every row of the view onto the same key.",
)
}
pub fn flow_dictionary_source_unsupported() -> Diagnostic {
flow_diagnostic(
"FLOW_038",
"dictionaries cannot source deferred or transactional views".to_string(),
"Dictionary entries are single-version sequence-like state with no change feed. Query the \
dictionary directly with FROM namespace::dictionary instead.",
)
}
pub fn flow_queue_source_unsupported() -> Diagnostic {
flow_diagnostic(
"FLOW_039",
"queues cannot source deferred or transactional views".to_string(),
"Queue items are claimed and acknowledged by consumers rather than streamed. Query the \
queue directly with FROM namespace::queue instead.",
)
}
pub fn flow_rolling_lag_requires_event_time(flow: &str) -> Diagnostic {
Diagnostic {
code: "FLOW_043".to_string(),
rql: None,
message: format!("{flow} uses a rolling window with `lag` but its sources supply no event time"),
column: None,
fragment: Fragment::None,
label: None,
help: Some(
"lag holds a rolling window open for out-of-order arrivals, which only has meaning against a \
source-supplied event time. Declare `with { time: event(<column>) }` on the source \
object this flow reads, or remove the lag."
.to_string(),
),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_join_retention_requires_event_time(flow: &str) -> Diagnostic {
Diagnostic {
code: "FLOW_049".to_string(),
rql: None,
message: format!("{flow} declares a join retention but its sources supply no event time"),
column: None,
fragment: Fragment::None,
label: None,
help: Some(
"a join retention frees a row once the watermark passes its own event time, which only has \
meaning against a source-supplied event time. Declare `with { time: event(<column>) }` on the \
source object this flow reads, or remove the retention."
.to_string(),
),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_catch_up_read_failed(from: u64, up_to: u64, cause: &str) -> Diagnostic {
Diagnostic {
code: "FLOW_046".to_string(),
rql: None,
message: format!("cdc catch-up read for versions ({from}, {up_to}] failed: {cause}"),
column: None,
fragment: Fragment::None,
label: None,
help: Some("The flow catch-up loader could not read the durable CDC log on behalf of a \
lagging flow. The flow retries with backoff and is poisoned if the failure \
persists; check the CDC storage for corruption or exhaustion."
.to_string()),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_span_on_unageable_node(flow: &str, operator: &str) -> Diagnostic {
Diagnostic {
code: "FLOW_045".to_string(),
rql: None,
message: format!("{operator} in {flow} declares a retention span but holds no state to age"),
column: None,
fragment: Fragment::None,
label: None,
help: Some(format!(
"Spans are only meaningful on operators that keep keyed state - join, distinct, append, \
apply and aggregate. {operator} keeps none, so the span would be accepted and never \
consulted. Move the span to the stateful operator downstream, or remove it."
)),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_guest_key_too_wide(len: usize) -> Diagnostic {
flow_diagnostic(
"FLOW_051",
format!("a guest row mapping key is {} bytes, the key holds at most 16", len),
"A guest operator supplied a row mapping key wider than the key can hold. Shorten the key or hash it \
to sixteen bytes before passing it; the host refuses it rather than truncating it, because a \
truncated key silently collides with every other key sharing its first sixteen bytes.",
)
}
pub fn flow_state_batch_key_unframed(len: usize) -> Diagnostic {
flow_diagnostic(
"FLOW_086",
format!("a batch state key of {} bytes names no known keyspace", len),
"Batch state writes take only keys framed with a registered keyspace; the empty key is a range bound, \
never a key.",
)
}
pub fn flow_digest_accuracy_not_a_literal(output: &str, function: &str) -> Diagnostic {
let example = match function {
"stats::approx_percentile" => "stats::approx_percentile(latency, 0.99, 0.01)",
_ => "stats::digest(latency, 0.01)",
};
flow_diagnostic(
"FLOW_052",
format!(
"aggregate output '{}' passes {} an accuracy that is not a decimal number literal",
output, function
),
&format!(
"The accuracy is part of the digest type, so it is fixed when the view is created: write it as \
a number literal, for example {}.",
example
),
)
}
pub fn flow_digest_accuracy_out_of_range(output: &str, function: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_053",
format!("aggregate output '{}' passes {} an accuracy outside 0.001 to 0.1", output, function),
"A digest answers within its accuracy as a relative error; pick an accuracy between 0.001 and 0.1 \
inclusive.",
)
}
pub fn flow_digest_accuracy_not_whole_ppm(output: &str, function: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_054",
format!(
"aggregate output '{}' passes {} an accuracy that is not a whole number of parts per million",
output, function
),
"A digest stores its accuracy in parts per million, so an accuracy such as 0.0100005 has no exact \
form; use at most six decimal places.",
)
}
pub fn flow_digest_accuracy_required(function: &str, input: ValueType) -> Diagnostic {
let example = match function {
"stats::approx_percentile" => "stats::approx_percentile(x, 0.99, 0.01)",
_ => "stats::digest(x, 0.01)",
};
flow_diagnostic(
"FLOW_055",
format!("{} over a {} input needs an accuracy argument", function, input),
&format!(
"Only a digest input takes no accuracy, because its accuracy comes from its type. Write {} for \
any other input.",
example
),
)
}
pub fn flow_digest_accuracy_given_for_digest(function: &str, input: ValueType) -> Diagnostic {
flow_diagnostic(
"FLOW_056",
format!("{} over a {} input takes no accuracy argument", function, input),
"The accuracy of a digest input comes from the digest type; remove the argument.",
)
}
pub fn flow_digest_input_rejected(function: &str, cause: String) -> Diagnostic {
flow_diagnostic(
"FLOW_057",
format!("{} cannot take its input: {}", function, cause),
"A digest takes ints, floats and durations without a month part, and every value of one input \
must have one type.",
)
}
pub fn flow_percentile_argument_count(output: &str, function: &str, actual: usize) -> Diagnostic {
flow_diagnostic(
"FLOW_058",
format!(
"aggregate output '{}' calls {} with {} arguments, but it takes 2 or 3",
output, function, actual
),
"Write stats::approx_percentile(x, p, accuracy) over a column or scalar expression, or \
stats::approx_percentile(d, p) over a digest.",
)
}
pub fn flow_percentile_not_a_literal(output: &str, function: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_059",
format!("aggregate output '{}' passes {} a p that is not a decimal number literal", output, function),
"Inside a window or aggregate the percentile is fixed when the view is created: write p as a number \
literal, for example stats::approx_percentile(latency, 0.99, 0.01).",
)
}
pub fn flow_percentile_out_of_range(output: &str, function: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_060",
format!("aggregate output '{}' passes {} a p outside 0 to 1", output, function),
"p is the fraction of values at or below the answer: 0.5 is the median and 0.99 the 99th percentile; \
pick a p between 0 and 1 inclusive.",
)
}
pub fn flow_view_calls_script_routine(name: &str, fragment: Fragment) -> Diagnostic {
Diagnostic {
code: "FLOW_061".to_string(),
rql: None,
message: format!("a deferred view cannot call '{}', a routine defined by the script", name),
column: None,
fragment,
label: Some("script routine called in a view".to_string()),
help: Some(
"A view flow keeps running after the script ends, so it only sees built-in routines. Inline the \
routine body into the view query instead."
.to_string(),
),
notes: vec![],
cause: None,
operator_chain: None,
}
}
pub fn flow_step_panicked(reason: String) -> Diagnostic {
flow_diagnostic(
"FLOW_062",
format!("a flow step panicked: {}", reason),
"An operator in this view panicked instead of returning an error. The flow retries a few times and is \
then poisoned, which the flow subsystem reports as degraded health. Fix the operator so it returns an \
error.",
)
}
pub fn flow_operator_with_count_span(key: &str, count: u64) -> Diagnostic {
flow_diagnostic(
"FLOW_063",
format!("operator setting '{}' is a count ({}), but this operator seals by time", key, count),
"Declare the setting as a duration, for example 30s, or use an operator that seals by row count.",
)
}
pub fn flow_operator_with_immutable_not_below_lateness(immutable: Duration, lateness: Duration) -> Diagnostic {
flow_diagnostic(
"FLOW_064",
format!("immutable {} must be strictly less than lateness {}", immutable, lateness),
"A window becomes immutable before it seals, so immutable must be shorter than lateness.",
)
}
pub fn flow_operator_with_window_missing() -> Diagnostic {
flow_diagnostic(
"FLOW_065",
"this operator needs 'window' in its with block".to_string(),
"Add a window to the apply, for example with { window: tumbling, duration: 1m }.",
)
}
pub fn flow_operator_with_pane_missing() -> Diagnostic {
flow_diagnostic(
"FLOW_075",
"this operator needs 'pane' in its rolling window's with block".to_string(),
"Add a pane to the apply, for example with { window: rolling, duration: 1h, pane: 1s }.",
)
}
pub fn flow_operator_with_window_kind_unsupported(kind: &str, supported: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_066",
format!("window '{}' is not supported by this operator, use '{}'", kind, supported),
"Change the window kind in the apply's with block.",
)
}
pub fn flow_operator_with_window_not_supported(kind: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_067",
format!("this operator takes no window, but window '{}' was given", kind),
"Remove window and its size keys from the apply's with block.",
)
}
pub fn flow_operator_with_window_size_count(count: u64) -> Diagnostic {
flow_diagnostic(
"FLOW_068",
format!("the window size is a count ({}), but this operator windows by time", count),
"Declare the size as a duration, for example duration: 1m.",
)
}
pub fn flow_operator_with_window_size_duration(size: Duration) -> Diagnostic {
flow_diagnostic(
"FLOW_069",
format!("the window size is a duration ({}), but this operator counts slots", size),
"Declare the size as a slot count, for example slots: 1.",
)
}
pub fn flow_operator_with_window_slide_count(count: u64) -> Diagnostic {
flow_diagnostic(
"FLOW_076",
format!("the window slide is a count ({}), but this operator windows by time", count),
"Declare the slide as a duration, for example slide: 30s.",
)
}
pub fn flow_operator_with_window_slide_duration(slide: Duration) -> Diagnostic {
flow_diagnostic(
"FLOW_077",
format!("the window slide is a duration ({}), but this operator counts slots", slide),
"Declare the slide as a slot count, for example slide: 1.",
)
}
pub fn flow_operator_with_session_lateness(lateness: Duration) -> Diagnostic {
flow_diagnostic(
"FLOW_078",
format!("a session window seals on its gap and takes no lateness (lateness: {})", lateness),
"Remove lateness, or set lateness: 0s.",
)
}
pub fn flow_operator_with_session_zero_gap() -> Diagnostic {
flow_diagnostic(
"FLOW_079",
"a session window needs a gap above zero".to_string(),
"Set gap to a positive duration.",
)
}
pub fn flow_operator_with_retention_not_supported() -> Diagnostic {
flow_diagnostic(
"FLOW_080",
"this operator takes no 'retention'".to_string(),
"Only a managed operator frees state by retention. Remove retention from the with block.",
)
}
pub fn flow_operator_with_retention_below_lateness(retention: Duration, lateness: Duration) -> Diagnostic {
flow_diagnostic(
"FLOW_081",
format!("retention {} must not be below lateness {}", retention, lateness),
"A managed operator's state must outlive its output hold, so retention must be at least lateness.",
)
}
pub fn flow_operator_with_duration_span(key: &str, duration: Duration) -> Diagnostic {
flow_diagnostic(
"FLOW_070",
format!("operator setting '{}' is a duration ({}), but this operator seals by count", key, duration),
"Declare the setting as a count, for example lateness: 150.",
)
}
pub fn flow_operator_with_not_accepted() -> Diagnostic {
flow_diagnostic(
"FLOW_071",
"a nostate operator takes no 'with'".to_string(),
"Remove the with block from this apply.",
)
}
pub fn flow_operator_retention_required() -> Diagnostic {
flow_diagnostic(
"FLOW_072",
"a managed operator needs 'retention' or 'lateness'".to_string(),
"Declare retention in the with block; lateness alone also bounds the state.",
)
}
pub fn flow_managed_operator_requires_event_time(flow: &str, operator: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_073",
format!("{flow} applies managed operator '{operator}' but its sources supply no event time"),
"a managed operator's state is freed once the watermark passes its last write plus retention, which only \
has meaning against a source-supplied event time. Declare `with { time: event(<column>) }` on the \
source object this flow reads.",
)
}
pub fn flow_operator_timer_kind_reserved(kind: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_074",
format!("timer kind '{kind}' is reserved for the engine"),
"Arm a Seal, Grace, RowTtl or Maintenance timer instead.",
)
}
pub fn extern_library_load_failed(path: &str, cause: String) -> Diagnostic {
flow_diagnostic(
"FLOW_087",
format!("extern operator library failed to load: {}: {}", path, cause),
"The shared library exports the operator magic symbol but the dynamic loader rejected it. Check its \
dependencies and that it was built for this platform.",
)
}
pub fn flow_transactional_not_supported(what: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_084",
format!("not supported in transactional views: {}", what),
"Transactional views support table sources, transactional view sources, filter, map, extend and \
append, with table storage and no ttl.",
)
}
pub fn flow_transactional_reads_deferred_view(name: &str) -> Diagnostic {
flow_diagnostic(
"FLOW_085",
format!("transactional view cannot read deferred view {}", name),
"A transactional view updates inside the txn, but a deferred view updates after commit. Read the \
source table instead.",
)
}