razeri-plugin-contract 0.16.0

The dialect contract Razeri plugins are written against — rendering traits, data types, platform identity
Documentation

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 Razeri's 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:

Trait What it renders
SqlRenderer Core SQL — the four required methods
ExpressionRenderer Expressions, with ANSI defaults
DdlRenderer DDL, with ANSI defaults
DmlRenderer DML, with ANSI defaults
ProcedureRenderer Stored procedures and functions
FormulaRenderer Formulas, with ANSI defaults
TaskRenderer Scheduled tasks
ReplicationRenderer Data extraction, loading and key-function deployment
StorageRenderer Partitioning, compression and index strategy
DialectCapabilities What the platform's SQL can and cannot do

The vocabulary those traits speak: DataType and DataTypeRef, the StoragePlan a StorageRenderer translates, the StoragePlatform a storage plugin names, and DialectFamily, the platform's own identity.

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, ScalarFunctionForm and TableFunctionForm, the routine grammars it speaks. Each of the three carries a Custom arm for a grammar Razeri does not know: your dialect names it and renders the routine itself, from the RoutineSpec Razeri hands over.

The spelling a source gives a day — date_format. One import arrives writing 1/1/2015 in one column and 2015-01-01T00:00:00Z in the next, and no two engines read either of them the same way. The design states the spelling, this crate reads it once into a list of directives, and ExpressionRenderer::render_parse_datetime is where your dialect translates that list into whatever pattern language your engine has. A directive your engine cannot say is a refusal naming it, never a guess.

The way a source writes an amount — number_format. The same file writes a price $2,899.99 , and the symbol, the thousands mark and the trailing space 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 ExpressionRenderer::render_parse_number is where your dialect converts what is left. Three shared shapes are here for the three kinds of engine: one with a conversion that answers nothing, one that needs a shape guard in front of one that does not, and one with neither.

The questions asked of a warehouse that is already there — SqlRenderer::render_catalog_columns_query and render_catalog_object_times_query. Razeri can read a design and say what it would build; how much of a running warehouse it actually built is a question only that warehouse answers. So your dialect spells the two queries that ask it — one row per column, and the moment each object was created where your engine records one — and catalog_system_schemas names the schemas a scan of the whole warehouse passes over. The standard's information_schema is what the first one answers by default, and None is a complete answer to either: an engine that keeps no catalog Razeri can read is named, not reported as an empty warehouse.

The fan-out an engine states — flatten. One row holding an array becomes one row per element, and no two engine families spell that the same way, so the dialect's render_flatten answers with the whole shape — the FROM item, the ordinal and one expression per field — rather than filling in a template. The ordinal it returns counts from one on every engine, because the element's surrogate key is derived from its position.

The surrogate key's composition — sid::sid_expression turns a SidSpelling into the key expression an engine runs. It is here 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 Razeri's repository can check.

AnsiDialect is a complete worked implementation of the required methods, and render_data_type_ansi is the ANSI type mapping to start from.

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. Leave it alone and your engine states nothing: a design naming a spelling is refused, with your engine named.
  • SqlRenderer::delimited_file_relation_in_encoding reads a delimited file whose bytes are not UTF-8. Leave it alone and a source naming a fallback encoding is declined, with your engine named, rather than read as some other language.

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. Leave it alone and you get the standard's information_schema, which most engines carry; answer None where yours has no catalog to read.
  • SqlRenderer::render_catalog_object_times_query asks when each object was created. Leave it alone and the answer is None, which is the truthful one on every engine that records no such moment.
  • SqlRenderer::catalog_system_schemas names the schemas a scan of a whole warehouse passes over. Leave it alone and it is information_schema alone; add the others your engine keeps its own bookkeeping in.
  • SqlRenderer::catalog_scope_predicate is the WHERE clause both catalog queries share — one named schema, or every schema that is not your engine's own. Leave it alone unless your catalog wants its scope written differently.
  • ExpressionRenderer::render_parse_number renders the reading of a value the source writes its own way into a number. Leave it alone and your engine states nothing: a number column naming a picture is refused, with your engine named.
  • StorageRenderer::slice_replacement_is_procedure_body says whether the slice replacement you rendered runs on its own or only inside a procedure. Leave it alone and Razeri reads the shape of your answer — a block opened by IF and closed by END IF is taken for a procedure body — which is a guess your engine can settle.

What 0.11.0 adds to the method set

  • DialectCapabilities::server_version_query states how to ask a running server its version: one row whose first word is the version, in the spelling your dialect declares. Leave it alone and nothing is asked: a deploy script for your engine sends no question.
  • DialectCapabilities::parse_server_version reads the version out of that answer. Leave it alone and the first word is read, which is what a deploy script reads too.
  • ExpressionRenderer::render_read_only_refusal writes a statement that reads a table, changes nothing, and stops with a sentence where a row meets a condition. Leave it alone and it is None: a load's refusal stays a statement that would write, stopped by render_refusal, as before.
  • ProcedureRenderer::render_call_for_each_row writes a statement that calls a procedure once for every row of a query. Leave it alone and it is None: a fill that has to make a partition for every day a file holds before staging it is refused on your engine, by name, with the calls to make by hand.

Beside the method set, EngineVersion is new: a version of an engine, read from 17, 8.4, 2022 or 23ai, which razeri-plugin-api's DialectInfo::versions lists. 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 your engine's own spelling, which is where reading a written amount starts.

What 0.16.0 changes

  • A view other objects read is replaced on an engine that will not replace it in place: answer false to DdlRenderer::replaces_a_view_that_is_read, and Razeri hands each view the objects that read it, which DdlRenderer::render_view_replacement drops first.
  • SqlRenderer::is_reserved_word says which words your engine reserves, the SQL standard's by default, and Razeri quotes the short names it writes inside a statement where the word is reserved.
  • FormulaRenderer::supports_split, supports_regexp_like, supports_regexp_extract and supports_ordered_string_agg let a dialect say its engine lacks a function, and a formula that needs it is refused by name.
  • ColumnEvolution::RequireColumn is the step that requires a column once every row holds a value.
  • Every change is additive: a dialect that compiled against 0.15.0 compiles unchanged. The CHANGELOG.md in this package says more.

What 0.15.0 changes

  • A load opens its own day's partitions: StorageRenderer::partition_extend_call answers an ExtendCall, which says whether the call runs inside the load or before its transaction opens. It replaces partition_extend_call_before_write.
  • EntityRef carries a TableRole — written, landing or identity — in place of is_identity_table.
  • DialectCapabilities::type_limits states the digits of a second and the fixed lengths an engine holds.
  • A time says whether its digits of a second were stated: TimeDataType::precision is an Option<u8>, and timestamp(0) and time(0) are whole seconds.
  • The number readings ask the shape in flat tests and take a ClassTest; every reading asks for a digit before the first thousands mark.
  • The CHANGELOG.md in this package says, change by change, what a dialect changes to compile.

What 0.14.0 changes

  • The surrogate key is the hash of the value as stored: sid::sid_expression and its kin no longer fold each part, and a composite escapes each part before the join. They take a sid::Backslash, read with Backslash::of(&your_dialect).
  • What Razeri used to decide by an engine's name is asked of the dialect, each with a default that keeps today's answer: bulk_load_form, render_refresh_statistics, update_from_takes_a_source_naming_the_targets_columns, render_view_options, render_join_options, render_table_storage_options, render_drop_column, render_drop_view, render_create_table_if_absent, EvolutionAnswers::drops_a_column_in_place_before_an_index_column, begin_leaves_the_commit_to_the_client, catalogue_reach, render_comparable, text_length_unit and render_bitwise_operator.
  • keeps_a_time_zone says whether an engine has a type that keeps a time zone.
  • text_limits states the longest varchar an engine takes, and render_not_an_iso_day how an engine that holds a date as text finds one not written as an ISO date.
  • The number readers for an engine with no pattern match hold a value to the picture's shape before its marks come out, so one picture reads the same values on every engine.
  • render_create_task, render_bulk_load and render_procedure_log answer Option<String>: None is how an engine with no scheduler, no load statement or no log statement says so. TaskRenderConfig carries enabled.
  • The CHANGELOG.md in this package says, change by change, what a dialect changes to compile.

What 0.13.0 changes

  • UpsertForm::OnConflictDoUpdate is new: an engine with no MERGE whose upsert is INSERT … ON CONFLICT … DO UPDATE answers MergeShape::Upsert(UpsertForm::OnConflictDoUpdate), and Razeri writes its core loads in that statement.
  • SidSpelling::ascii_fold is new: AsciiFold::Replacements by default, or AsciiFold::Functions naming the functions your engine holds that change the case of a to z and nothing else. A spelling built field by field adds the field; one ending ..ANSI_SID_SPELLING is unchanged.
  • routine_render::render_table_function_as writes DROP FUNCTION IF EXISTS <name>(<input types>); above a replaced PostgreSQL function, which the engine will not replace when its returned columns changed.
  • routine_render::render_procedure_as takes your dialect's supports_procedure_comment() as its third argument, and leaves the description out where it is false.

What 0.12.0 changes

  • SqlRenderer::fold_schema_into_name is new: on an engine with no schemas, the name an object takes when the schema is folded into it. Leave it alone and the schema is dropped, as before, and a design whose layers share a table name is refused.
  • FormulaRenderer::render_power_operator, render_ilike_operator, render_not_ilike_operator and render_concat_operator answer Option<String>: None says the engine has no such operator, and a formula that needs it is refused by name. A dialect that overrides any of them changes its signature; the CHANGELOG.md in this crate's package says how.
  • Concat::Operator joins a key's parts with an operator: Concat::Operator("||").

Stability

This crate and razeri-plugin-api follow semantic versioning while the major version is zero, held to the stricter reading above. A trait gains a method only with a default body, so a new method costs an existing plugin no line of code. A change that does break a plugin's build — a method whose answer changes type, an enum a match has to name a new variant of — comes only in a minor release, and the changelog says what to change. A method that leaves does so as #[deprecated] for one minor version before it is removed, so a plugin sees the warning before the removal, not after.

What is deliberately not in here

Razeri's design model and every renderer that produces Razeri's own output. A plugin says what its engine spells; Razeri composes.

License

Dual-licensed under MIT or Apache 2.0, at your option. Plugins belong to their authors.

Security

Report a vulnerability as Razeri's security policy says.