Skip to main content

razeri_plugin_contract/
lib.rs

1// Copyright (C) 2026 Gabriel Shea
2// Licensed under either of the Apache License, Version 2.0 or the MIT
3// license, at your option.
4
5#![warn(missing_docs)]
6
7//! # razeri-plugin-contract
8//!
9//! **The contract a Razeri dialect plugin is written against.**
10//!
11//! This crate and `razeri-plugin-api` are the two open crates in Razeri's
12//! workspace. Everything a plugin author needs in order to *describe* and
13//! *render* a database platform lives here; nothing else does. It depends on
14//! `serde` and on nothing else at all, which is what lets a plugin be built,
15//! published and consumed outside this repository.
16//!
17//! ## What is in here
18//!
19//! **The rendering hierarchy** — ten traits, layered so that a new dialect
20//! implements four required methods and inherits ANSI-standard behaviour for
21//! the rest:
22//!
23//! 1. [`SqlRenderer`] — core SQL rendering (the four required methods)
24//! 2. [`ExpressionRenderer`] — expressions, with ANSI defaults
25//! 3. [`DdlRenderer`] — DDL, with ANSI defaults
26//! 4. [`DmlRenderer`] — DML, with ANSI defaults
27//! 5. [`ProcedureRenderer`] — stored procedures and functions
28//! 6. [`FormulaRenderer`] — formulas, with ANSI defaults
29//! 7. [`TaskRenderer`] — scheduled tasks
30//! 8. [`ReplicationRenderer`] — data extraction, loading and key-function deployment
31//! 9. [`StorageRenderer`] — partitioning, compression and index strategy
32//! 10. [`DialectCapabilities`] — what the platform's SQL can and cannot do
33//!
34//! **The vocabulary those traits speak**: [`DataType`] and [`DataTypeRef`], the
35//! [`storage_strategy::StoragePlan`] a [`StorageRenderer`] translates, the
36//! [`StoragePlatform`] a storage plugin names, and [`DialectFamily`], the
37//! platform's own identity.
38//!
39//! **The spelling a source gives a day** — [`date_format`]. One import
40//! arrives with `1/1/2015` in one column and `2015-01-01T00:00:00Z` in the
41//! next; the design states the spelling, this crate reads it once into a list
42//! of directives, and each engine translates that list into its own pattern
43//! language through [`ExpressionRenderer::render_parse_datetime`]. A directive
44//! an engine cannot spell is refused by name.
45//!
46//! **The way a source writes an amount** — [`number_format`]. A file writes a
47//! price `$2,899.99 `, where the symbol, the thousands mark and the space
48//! around it are not part of the number. The design states the picture, this
49//! crate reads it once into the marks that are not part of the value, and each
50//! engine takes those marks out and converts what is left through
51//! [`ExpressionRenderer::render_parse_number`]. An engine that states nothing
52//! is refused by name rather than guessed for.
53//!
54//! **The questions asked of a warehouse that is already there** —
55//! [`SqlRenderer::render_catalog_columns_query`] and
56//! [`SqlRenderer::render_catalog_object_times_query`]. A design says what
57//! would be built; how much of a running warehouse was built is a question
58//! only that warehouse answers, so each engine spells the two queries that
59//! ask it and names the schemas a whole-warehouse scan passes over through
60//! [`SqlRenderer::catalog_system_schemas`]. `None` is a complete answer to
61//! either query: an engine with no catalog to read is named rather than
62//! reported as an empty warehouse.
63//!
64//! **The version of an engine** — [`EngineVersion`]. A platform is an engine
65//! at a version: `postgres`, or `postgres@17`. A dialect declares the versions
66//! it answers for, and states how to ask a running server its version through
67//! [`DialectCapabilities::server_version_query`], so a deploy script can refuse
68//! a server older than the bundle before anything runs.
69//!
70//! **The fan-out an engine states** — [`flatten`]. One row holding an array
71//! becomes one row per element, and every engine spells that differently, so
72//! the dialect answers with the whole shape rather than filling in a template.
73//!
74//! **The decisions a dialect states rather than a core crate deciding for it**,
75//! starting with [`SidSpelling`] — how this engine spells a surrogate key —
76//! and continuing with [`ProcedureForm`] and [`ScalarFunctionForm`], the
77//! routine grammars it speaks, [`MergeShape`], how it upserts, and
78//! [`EvolutionAnswers`], what it keeps when a table that already holds rows
79//! changes shape.
80//!
81//! **The surrogate key's composition** — [`sid`] turns a [`SidSpelling`] into
82//! the key expression an engine runs. It is here rather than in the product
83//! because a dialect author who cannot render their own key expression cannot
84//! run it either, and *"the same key on every engine"* is then a claim nobody
85//! outside this repository can check.
86//!
87//! **The named routine grammars, spelled** — [`routine_render`]. A dialect
88//! whose engine has stored procedures in a grammar of its own renders the whole
89//! `CREATE` itself, and *"PL/pgSQL with one tweak"* need not mean writing the
90//! whole thing from nothing. These three functions write out the grammar a
91//! dialect is nearly the same as, so it can start there and say what differs.
92//!
93//! ## What is deliberately not in here
94//!
95//! Razeri's design model, and the renderers that turn it into Razeri's own
96//! output. A plugin says what its engine spells; Razeri composes. That division
97//! is why one contract crate can be open while the product is not.
98//!
99//! The routine grammars are the one thing on both sides of that line, and they
100//! are here because they belong to the engines rather than to Razeri's design: a
101//! plugin author who cannot read the grammar next to theirs has to guess at it.
102//!
103//! ## How this crate is versioned
104//!
105//! While the major version is zero:
106//!
107//! - **a change to a trait's method set bumps the minor version** — a method
108//!   added, removed or renamed, whether or not it carries a default body;
109//! - **documentation and fixes bump the patch version.**
110//!
111//! The rule is about who decides when a plugin's behaviour changes. Cargo
112//! flows a patch release to every plugin pinned at the minor the next time it
113//! builds, and flows a minor release to nobody. A defaulted method costs an
114//! existing plugin no line of code, and it still changes what that plugin
115//! *says*: an engine that has not implemented the new method now answers the
116//! default, and its users meet whatever Razeri does with that answer — a
117//! refusal they had never seen, arriving because somebody else released. So a
118//! new method is opted into, and the plugin author chooses the day.
119//!
120//! This is stricter than semantic versioning requires of a zero-major crate,
121//! deliberately: the looser reading saves a version number and spends a
122//! plugin author's afternoon.
123//!
124//! ### What 0.5.0 adds to the method set
125//!
126//! - [`StorageRenderer::render_partition_retain_procedure`] takes the
127//!   companion table a load sets rows aside in, so one call to a retention
128//!   procedure reaches both tables.
129//! - [`ExpressionRenderer::render_parse_datetime`] renders the parse of a
130//!   value the source spells its own way into a date or a timestamp.
131//! - [`SqlRenderer::delimited_file_relation_in_encoding`] reads a delimited
132//!   file whose bytes are not UTF-8, where the engine states a way to.
133//!
134//! ### What 0.6.0 adds to the method set
135//!
136//! - [`SqlRenderer::render_catalog_columns_query`] asks a running warehouse
137//!   what it holds, one row per column.
138//! - [`SqlRenderer::render_catalog_object_times_query`] asks when each object
139//!   was created, where the engine records it at all.
140//! - [`SqlRenderer::catalog_system_schemas`] names the schemas a
141//!   whole-warehouse scan passes over, and
142//!   [`SqlRenderer::catalog_scope_predicate`] is the `WHERE` clause both
143//!   catalog queries share.
144//! - [`ExpressionRenderer::render_parse_number`] renders the reading of a
145//!   value the source writes its own way into a number.
146//! - [`StorageRenderer::slice_replacement_is_procedure_body`] says whether the
147//!   slice replacement this engine rendered runs on its own or only inside a
148//!   procedure. Left alone, the shape of the answer is read instead.
149//!
150//! ### What 0.11.0 adds to the method set
151//!
152//! - [`DialectCapabilities::server_version_query`] states how to ask a
153//!   running server its version, and
154//!   [`DialectCapabilities::parse_server_version`] reads the answer.
155//! - [`ExpressionRenderer::render_read_only_refusal`] stops with a sentence
156//!   from a statement that only reads, where the engine has one.
157//! - [`ProcedureRenderer::render_call_for_each_row`] calls a procedure once
158//!   for every row of a query, where the engine has a statement that loops.
159//!
160//! Beside the method set, [`EngineVersion`] is new: a version of an engine,
161//! read from `17`, `8.4`, `2022` or `23ai`, and [`split_platform`] reads
162//! `postgres@17` into the engine and the version.
163//!
164//! Beside the 0.6.0 method set, [`sid::normalize_trim`] is now public: the `TRIM` in
165//! an engine's own spelling, which the reading of a written amount starts from.
166//!
167//! ### What 0.14.0 changes in the method set
168//!
169//! - The key's composers hash the value as stored, and a composite escapes
170//!   each part: [`sid::sid_expression`] takes a [`sid::Backslash`].
171//! - New, each with a default: [`SqlRenderer::bulk_load_form`],
172//!   [`SqlRenderer::render_refresh_statistics`],
173//!   [`SqlRenderer::update_from_takes_a_source_naming_the_targets_columns`],
174//!   [`SqlRenderer::render_view_options`], [`SqlRenderer::render_join_options`],
175//!   [`DdlRenderer::render_table_storage_options`],
176//!   [`DdlRenderer::render_drop_column`], [`DdlRenderer::render_drop_view`],
177//!   [`DdlRenderer::render_create_table_if_absent`],
178//!   [`DialectCapabilities::begin_leaves_the_commit_to_the_client`],
179//!   [`DialectCapabilities::catalogue_reach`],
180//!   [`DialectCapabilities::text_limits`],
181//!   [`DialectCapabilities::keeps_a_time_zone`],
182//!   [`ExpressionRenderer::render_not_an_iso_day`],
183//!   [`ExpressionRenderer::render_comparable`],
184//!   [`ExpressionRenderer::text_length_unit`] and
185//!   [`FormulaRenderer::render_bitwise_operator`].
186//! - [`TaskRenderer::render_create_task`], [`TaskRenderer::render_bulk_load`]
187//!   and [`SqlRenderer::render_procedure_log`] answer `Option<String>`.
188
189/// This crate's version, as a plugin was compiled against it.
190///
191/// A plugin loaded from a directory is refused unless it was built against
192/// the same version, from the same source, as the razeri that loads it.
193pub const VERSION: &str = env!("CARGO_PKG_VERSION");
194
195/// A fingerprint of this crate's source. Two builds carrying the same version
196/// over different source have different fingerprints.
197pub const SOURCE_FINGERPRINT: u64 =
198    match u64::from_str_radix(env!("RAZERI_PLUGIN_CONTRACT_SOURCE"), 16) {
199        Ok(fingerprint) => fingerprint,
200        Err(_) => panic!("the build script writes the fingerprint in hexadecimal"),
201    };
202
203pub mod agnostic;
204pub mod bulk_load_form;
205pub mod capabilities;
206pub mod constraint_enforcement;
207pub mod data_type;
208pub mod date_format;
209pub mod dialect_family;
210pub mod engine_version;
211pub mod evolution;
212pub mod flatten;
213pub mod formula_renderer;
214pub mod large_object;
215pub mod merge_shape;
216pub mod number_format;
217pub mod procedure_log_level;
218pub mod renderer;
219pub mod replication;
220pub mod routine_form;
221pub mod routine_render;
222pub mod routine_spec;
223pub mod sid;
224pub mod sid_spelling;
225pub mod storage_platform;
226pub mod storage_renderer;
227pub mod storage_strategy;
228pub mod task_renderer;
229pub mod value_text;
230
231pub use agnostic::*;
232pub use bulk_load_form::BulkLoadForm;
233pub use capabilities::*;
234pub use constraint_enforcement::*;
235pub use data_type::{DataType, DataTypeRef};
236pub use date_format::{DateFormat, DateFormatError, FormatDirective, FormatPart};
237pub use dialect_family::DialectFamily;
238pub use engine_version::{EngineVersion, EngineVersionError, split_platform};
239pub use evolution::*;
240pub use flatten::{Flatten, FlattenField, FlattenRequest};
241pub use formula_renderer::*;
242pub use large_object::{HeldAs, TextLengthUnit};
243pub use merge_shape::*;
244pub use number_format::{Negatives, NumberFormat, NumberFormatError};
245pub use procedure_log_level::ProcedureLogLevel;
246pub use renderer::*;
247pub use replication::*;
248pub use routine_form::*;
249pub use routine_render::{
250    render_procedure_as, render_scalar_function_as, render_table_function_as,
251};
252pub use routine_spec::*;
253pub use sid::sid_expression;
254pub use sid_spelling::*;
255pub use storage_platform::StoragePlatform;
256pub use storage_renderer::*;
257pub use task_renderer::*;
258pub use value_text::{
259    CanonicalText, DOUBLE_DIGITS, FLOAT_TEXT_FUNCTION, FloatArithmetic, FloatParts, SINGLE_DIGITS,
260    SINGLE_FLOAT_TEXT_FUNCTION, TextFunctions, UTC_OFFSET, ValueTextFunctions, computed_float_text,
261    float_text, float_text_function, in_short_lines, rtrim_fraction, signed_float_text, truth_text,
262};