razeri-plugin-contract 0.15.0

The dialect contract Razeri plugins are written against — rendering traits, data types, platform identity
Documentation
// Copyright (C) 2026 Gabriel Shea
// Licensed under either of the Apache License, Version 2.0 or the MIT
// license, at your option.

#![warn(missing_docs)]

//! # razeri-plugin-contract
//!
//! **The contract a Razeri dialect plugin is written against.**
//!
//! This crate and `razeri-plugin-api` are the two open crates in Razeri's
//! workspace. Everything a plugin author needs in order to *describe* and
//! *render* a database platform lives here; nothing else does. It depends on
//! `serde` and on nothing else at all, which is what lets a plugin be built,
//! published and consumed outside this repository.
//!
//! ## What is in here
//!
//! **The rendering hierarchy** — ten traits, layered so that a new dialect
//! implements four required methods and inherits ANSI-standard behaviour for
//! the rest:
//!
//! 1. [`SqlRenderer`] — core SQL rendering (the four required methods)
//! 2. [`ExpressionRenderer`] — expressions, with ANSI defaults
//! 3. [`DdlRenderer`] — DDL, with ANSI defaults
//! 4. [`DmlRenderer`] — DML, with ANSI defaults
//! 5. [`ProcedureRenderer`] — stored procedures and functions
//! 6. [`FormulaRenderer`] — formulas, with ANSI defaults
//! 7. [`TaskRenderer`] — scheduled tasks
//! 8. [`ReplicationRenderer`] — data extraction, loading and key-function deployment
//! 9. [`StorageRenderer`] — partitioning, compression and index strategy
//! 10. [`DialectCapabilities`] — what the platform's SQL can and cannot do
//!
//! **The vocabulary those traits speak**: [`DataType`] and [`DataTypeRef`], the
//! [`storage_strategy::StoragePlan`] a [`StorageRenderer`] translates, the
//! [`StoragePlatform`] a storage plugin names, and [`DialectFamily`], the
//! platform's own identity.
//!
//! **The spelling a source gives a day** — [`date_format`]. One import
//! arrives with `1/1/2015` in one column and `2015-01-01T00:00:00Z` in the
//! next; the design states the spelling, this crate reads it once into a list
//! of directives, and each engine translates that list into its own pattern
//! language through [`ExpressionRenderer::render_parse_datetime`]. A directive
//! an engine cannot spell is refused by name.
//!
//! **The way a source writes an amount** — [`number_format`]. A file writes a
//! price `$2,899.99 `, where the symbol, the thousands mark and the space
//! around it are not part of the number. The design states the picture, this
//! crate reads it once into the marks that are not part of the value, and each
//! engine takes those marks out and converts what is left through
//! [`ExpressionRenderer::render_parse_number`]. An engine that states nothing
//! is refused by name rather than guessed for.
//!
//! **The questions asked of a warehouse that is already there** —
//! [`SqlRenderer::render_catalog_columns_query`] and
//! [`SqlRenderer::render_catalog_object_times_query`]. A design says what
//! would be built; how much of a running warehouse was built is a question
//! only that warehouse answers, so each engine spells the two queries that
//! ask it and names the schemas a whole-warehouse scan passes over through
//! [`SqlRenderer::catalog_system_schemas`]. `None` is a complete answer to
//! either query: an engine with no catalog to read is named rather than
//! reported as an empty warehouse.
//!
//! **The version of an engine** — [`EngineVersion`]. A platform is an engine
//! at a version: `postgres`, or `postgres@17`. A dialect declares the versions
//! it answers for, and states how to ask a running server its version through
//! [`DialectCapabilities::server_version_query`], so a deploy script can refuse
//! a server older than the bundle before anything runs.
//!
//! **The fan-out an engine states** — [`flatten`]. One row holding an array
//! becomes one row per element, and every engine spells that differently, so
//! the dialect answers with the whole shape rather than filling in a template.
//!
//! **The decisions a dialect states rather than a core crate deciding for it**,
//! starting with [`SidSpelling`] — how this engine spells a surrogate key —
//! and continuing with [`ProcedureForm`] and [`ScalarFunctionForm`], the
//! routine grammars it speaks, [`MergeShape`], how it upserts, and
//! [`EvolutionAnswers`], what it keeps when a table that already holds rows
//! changes shape.
//!
//! **The surrogate key's composition** — [`sid`] turns a [`SidSpelling`] into
//! the key expression an engine runs. It is here rather than in the product
//! because a dialect author who cannot render their own key expression cannot
//! run it either, and *"the same key on every engine"* is then a claim nobody
//! outside this repository can check.
//!
//! **The named routine grammars, spelled** — [`routine_render`]. A dialect
//! whose engine has stored procedures in a grammar of its own renders the whole
//! `CREATE` itself, and *"PL/pgSQL with one tweak"* need not mean writing the
//! whole thing from nothing. These three functions write out the grammar a
//! dialect is nearly the same as, so it can start there and say what differs.
//!
//! ## What is deliberately not in here
//!
//! Razeri's design model, and the renderers that turn it into Razeri's own
//! output. A plugin says what its engine spells; Razeri composes. That division
//! is why one contract crate can be open while the product is not.
//!
//! The routine grammars are the one thing on both sides of that line, and they
//! are here because they belong to the engines rather than to Razeri's design: a
//! plugin author who cannot read the grammar next to theirs has to guess at it.
//!
//! ## How this crate is versioned
//!
//! While the major version is zero:
//!
//! - **a change to a trait's method set bumps the minor version** — a method
//!   added, removed or renamed, whether or not it carries a default body;
//! - **documentation and fixes bump the patch version.**
//!
//! The rule is about who decides when a plugin's behaviour changes. Cargo
//! flows a patch release to every plugin pinned at the minor the next time it
//! builds, and flows a minor release to nobody. A defaulted method costs an
//! existing plugin no line of code, and it still changes what that plugin
//! *says*: an engine that has not implemented the new method now answers the
//! default, and its users meet whatever Razeri does with that answer — a
//! refusal they had never seen, arriving because somebody else released. So a
//! new method is opted into, and the plugin author chooses the day.
//!
//! This is stricter than semantic versioning requires of a zero-major crate,
//! deliberately: the looser reading saves a version number and spends a
//! plugin author's afternoon.
//!
//! ### What 0.5.0 adds to the method set
//!
//! - [`StorageRenderer::render_partition_retain_procedure`] takes the
//!   companion table a load sets rows aside in, so one call to a retention
//!   procedure reaches both tables.
//! - [`ExpressionRenderer::render_parse_datetime`] renders the parse of a
//!   value the source spells its own way into a date or a timestamp.
//! - [`SqlRenderer::delimited_file_relation_in_encoding`] reads a delimited
//!   file whose bytes are not UTF-8, where the engine states a way to.
//!
//! ### What 0.6.0 adds to the method set
//!
//! - [`SqlRenderer::render_catalog_columns_query`] asks a running warehouse
//!   what it holds, one row per column.
//! - [`SqlRenderer::render_catalog_object_times_query`] asks when each object
//!   was created, where the engine records it at all.
//! - [`SqlRenderer::catalog_system_schemas`] names the schemas a
//!   whole-warehouse scan passes over, and
//!   [`SqlRenderer::catalog_scope_predicate`] is the `WHERE` clause both
//!   catalog queries share.
//! - [`ExpressionRenderer::render_parse_number`] renders the reading of a
//!   value the source writes its own way into a number.
//! - [`StorageRenderer::slice_replacement_is_procedure_body`] says whether the
//!   slice replacement this engine rendered runs on its own or only inside a
//!   procedure. Left alone, the shape of the answer is read instead.
//!
//! ### What 0.11.0 adds to the method set
//!
//! - [`DialectCapabilities::server_version_query`] states how to ask a
//!   running server its version, and
//!   [`DialectCapabilities::parse_server_version`] reads the answer.
//! - [`ExpressionRenderer::render_read_only_refusal`] stops with a sentence
//!   from a statement that only reads, where the engine has one.
//! - [`ProcedureRenderer::render_call_for_each_row`] calls a procedure once
//!   for every row of a query, where the engine has a statement that loops.
//!
//! Beside the method set, [`EngineVersion`] is new: a version of an engine,
//! read from `17`, `8.4`, `2022` or `23ai`, and [`split_platform`] reads
//! `postgres@17` into the engine and the version.
//!
//! Beside the 0.6.0 method set, [`sid::normalize_trim`] is now public: the `TRIM` in
//! an engine's own spelling, which the reading of a written amount starts from.
//!
//! ### What 0.15.0 changes in the method set
//!
//! - [`StorageRenderer::partition_extend_call`] replaces
//!   `partition_extend_call_before_write`, answering an [`ExtendCall`] that
//!   says where the call runs ([`ExtendRuns`]).
//! - [`EntityRef::role`] replaces `is_identity_table`, a [`TableRole`].
//! - New, with a default: [`DialectCapabilities::type_limits`], answering
//!   [`TypeLimits`].
//! - [`data_type::TimeDataType::precision`] is an `Option<u8>`, and a
//!   timestamp's 0 is whole seconds.
//! - [`number_format::read_with_safe_cast`] and
//!   [`number_format::read_with_digit_check`] take a
//!   [`number_format::ClassTest`], and read flat.
//!
//! ### What 0.14.0 changes in the method set
//!
//! - The key's composers hash the value as stored, and a composite escapes
//!   each part: [`sid::sid_expression`] takes a [`sid::Backslash`].
//! - New, each with a default: [`SqlRenderer::bulk_load_form`],
//!   [`SqlRenderer::render_refresh_statistics`],
//!   [`SqlRenderer::update_from_takes_a_source_naming_the_targets_columns`],
//!   [`SqlRenderer::render_view_options`], [`SqlRenderer::render_join_options`],
//!   [`DdlRenderer::render_table_storage_options`],
//!   [`DdlRenderer::render_drop_column`], [`DdlRenderer::render_drop_view`],
//!   [`DdlRenderer::render_create_table_if_absent`],
//!   [`DialectCapabilities::begin_leaves_the_commit_to_the_client`],
//!   [`DialectCapabilities::catalogue_reach`],
//!   [`DialectCapabilities::text_limits`],
//!   [`DialectCapabilities::keeps_a_time_zone`],
//!   [`ExpressionRenderer::render_not_an_iso_day`],
//!   [`ExpressionRenderer::render_comparable`],
//!   [`ExpressionRenderer::text_length_unit`] and
//!   [`FormulaRenderer::render_bitwise_operator`].
//! - [`TaskRenderer::render_create_task`], [`TaskRenderer::render_bulk_load`]
//!   and [`SqlRenderer::render_procedure_log`] answer `Option<String>`.

/// This crate's version, as a plugin was compiled against it.
///
/// A plugin loaded from a directory is refused unless it was built against
/// the same version, from the same source, as the razeri that loads it.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// A fingerprint of this crate's source. Two builds carrying the same version
/// over different source have different fingerprints.
pub const SOURCE_FINGERPRINT: u64 =
    match u64::from_str_radix(env!("RAZERI_PLUGIN_CONTRACT_SOURCE"), 16) {
        Ok(fingerprint) => fingerprint,
        Err(_) => panic!("the build script writes the fingerprint in hexadecimal"),
    };

pub mod agnostic;
pub mod bulk_load_form;
pub mod capabilities;
pub mod constraint_enforcement;
pub mod data_type;
pub mod date_format;
pub mod dialect_family;
pub mod engine_version;
pub mod evolution;
pub mod flatten;
pub mod formula_renderer;
pub mod large_object;
pub mod merge_shape;
pub mod number_format;
pub mod procedure_log_level;
pub mod renderer;
pub mod replication;
pub mod routine_form;
pub mod routine_render;
pub mod routine_spec;
pub mod sid;
pub mod sid_spelling;
pub mod storage_platform;
pub mod storage_renderer;
pub mod storage_strategy;
pub mod task_renderer;
pub mod value_text;

pub use agnostic::*;
pub use bulk_load_form::BulkLoadForm;
pub use capabilities::*;
pub use constraint_enforcement::*;
pub use data_type::{DataType, DataTypeRef};
pub use date_format::{DateFormat, DateFormatError, FormatDirective, FormatPart};
pub use dialect_family::DialectFamily;
pub use engine_version::{EngineVersion, EngineVersionError, split_platform};
pub use evolution::*;
pub use flatten::{Flatten, FlattenField, FlattenRequest};
pub use formula_renderer::*;
pub use large_object::{HeldAs, TextLengthUnit};
pub use merge_shape::*;
pub use number_format::{Negatives, NumberFormat, NumberFormatError};
pub use procedure_log_level::ProcedureLogLevel;
pub use renderer::*;
pub use replication::*;
pub use routine_form::*;
pub use routine_render::{
    render_procedure_as, render_scalar_function_as, render_table_function_as,
};
pub use routine_spec::*;
pub use sid::sid_expression;
pub use sid_spelling::*;
pub use storage_platform::StoragePlatform;
pub use storage_renderer::*;
pub use task_renderer::*;
pub use value_text::{
    CanonicalText, DOUBLE_DIGITS, FLOAT_TEXT_FUNCTION, FloatArithmetic, FloatParts, SINGLE_DIGITS,
    SINGLE_FLOAT_TEXT_FUNCTION, TextFunctions, UTC_OFFSET, ValueTextFunctions, computed_float_text,
    float_text, float_text_function, in_short_lines, rtrim_fraction, signed_float_text, truth_text,
};