Skip to main content

DialectCapabilities

Trait DialectCapabilities 

Source
pub trait DialectCapabilities: DdlRenderer {
Show 58 methods // Provided methods fn merge_shape(&self) -> MergeShape { ... } fn catalogue_reach(&self) -> CatalogueReach { ... } fn evolution_answers(&self) -> EvolutionAnswers { ... } fn supports_returning(&self) -> bool { ... } fn server_version_query(&self) -> Option<&str> { ... } fn parse_server_version(&self, answer: &str) -> Option<EngineVersion> { ... } fn supports_cte(&self) -> bool { ... } fn supports_recursive_cte(&self) -> bool { ... } fn supports_window_functions(&self) -> bool { ... } fn supports_qualify(&self) -> bool { ... } fn supports_groups_frame(&self) -> bool { ... } fn supports_nulls_ordering(&self) -> bool { ... } fn supports_identity(&self) -> bool { ... } fn supports_sequences(&self) -> bool { ... } fn supports_schemas(&self) -> bool { ... } fn routine_is_callable_across_schemas(&self) -> bool { ... } fn supports_bulk_load(&self) -> bool { ... } fn supports_stored_procedures(&self) -> bool { ... } fn supports_stored_functions(&self) -> bool { ... } fn supports_table_functions(&self) -> bool { ... } fn supports_tasks(&self) -> bool { ... } fn supports_partitioning(&self) -> bool { ... } fn supports_materialized_views(&self) -> bool { ... } fn supports_generated_columns(&self) -> bool { ... } fn supports_json(&self) -> bool { ... } fn supports_unsigned_integers(&self) -> bool { ... } fn supports_arrays(&self) -> bool { ... } fn renders_nested_type_natively(&self, data_type: &DataType) -> bool { ... } fn compares_nested_values(&self, data_type: &DataType) -> bool { ... } fn groups_nested_values(&self, data_type: &DataType) -> bool { ... } fn supports_boolean(&self) -> bool { ... } fn default_schema(&self) -> &'static str { ... } fn max_identifier_length(&self) -> usize { ... } fn max_columns(&self) -> usize { ... } fn max_parameters(&self) -> usize { ... } fn decimal_limits(&self) -> Option<DecimalLimits> { ... } fn text_limits(&self) -> Option<TextLimits> { ... } fn type_limits(&self) -> Option<TypeLimits> { ... } fn keeps_a_time_zone(&self) -> bool { ... } fn supports_function_overloading(&self) -> bool { ... } fn supports_merge_star_syntax(&self) -> bool { ... } fn schema_namespace_keyword(&self) -> &'static str { ... } fn supports_hash_comments(&self) -> bool { ... } fn batch_separator(&self) -> Option<&'static str> { ... } fn supports_multi_statement_transactions(&self) -> bool { ... } fn begin_transaction_keyword(&self) -> &'static str { ... } fn commit_transaction_keyword(&self) -> &'static str { ... } fn begin_leaves_the_commit_to_the_client(&self) -> bool { ... } fn truncate_commits_implicitly(&self) -> bool { ... } fn refusal_stops_the_statement(&self) -> bool { ... } fn delimited_file_reads_null_word_as_missing(&self) -> bool { ... } fn supports_partition_function(&self) -> bool { ... } fn supports_partition_split_merge(&self) -> bool { ... } fn supports_columnstore_index(&self) -> bool { ... } fn columnstore_is_table_engine(&self) -> bool { ... } fn supports_clustering_key(&self) -> bool { ... } fn supports_table_compression(&self) -> bool { ... } fn partition_requires_pk_includes_field(&self) -> bool { ... }
}
Expand description

Level 6 — Boolean capability flags with conservative defaults.

These flags tell the rendering layer what syntax features the platform supports. Defaults are false / conservative, so unknown platforms produce safe ANSI SQL.

§A fact is stated once

Thirteen facts about the statements an engine takes are asked both here and of the renderer a statement is written with, and they are stated only on the renderer: ten on SqlRenderer — supports_on_conflict, supports_insert_overwrite, supports_update_from, supports_named_windows, supports_compound_assignment, supports_window_frame_exclude, supports_merge_not_matched_by_source, merge_not_matched_keyword, uses_alter_table_update and supports_multi_table_insert — and three on DdlRenderer — supports_cascade_drop, supports_sync_drop and supports_force_drop.

This trait requires DdlRenderer, and so SqlRenderer, so a reader holding only the capabilities still asks capabilities.supports_on_conflict() and hears the renderer’s answer. There is no second answer to keep equal, and stating one here does not compile:

ⓘ
use razeri_plugin_contract::{DataType, DdlRenderer, DialectCapabilities, SqlRenderer};

struct MyDb;
impl SqlRenderer for MyDb {
    fn render_data_type(&self, _dt: &DataType) -> String { "TEXT".into() }
    fn quote_identifier(&self, name: &str) -> String { format!("\"{name}\"") }
    fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String { name.into() }
    fn render_current_timestamp(&self) -> String { "CURRENT_TIMESTAMP".into() }
}
impl DdlRenderer for MyDb {}
impl DialectCapabilities for MyDb {
    // The renderer is where this is said.
    fn supports_on_conflict(&self) -> bool { true }
}

Ask one of the thirteen by its path as SqlRenderer::supports_on_conflict(&d) or DdlRenderer::supports_cascade_drop(&d); a path through this trait does not reach a method it inherits.

Provided Methods§

Source

fn merge_shape(&self) -> MergeShape

How this engine performs the upsert Razeri’s generated load is.

See MergeShape. The default is a refusal, because the honest answer for an engine nobody has measured is that Razeri does not know how to load it — not that it spells its upsert the way MySQL does.

Source

fn catalogue_reach(&self) -> CatalogueReach

How much of the deployed schema a reconciliation can compare with the design here.

Razeri’s schema check reads the engine’s catalogue in one query: the ANSI information_schema.columns, a FULL OUTER JOIN against the design’s own columns, and a table value constructor with a derived column list, (VALUES ('a'), ('b')) AS designed (column_name). See CatalogueReach. The default is CatalogueReach::Nothing with a sentence saying Razeri has not written this engine’s catalogue query: an engine nobody has measured gets no check, and a warning that says why, rather than a query that fails.

Source

fn evolution_answers(&self) -> EvolutionAnswers

What this engine keeps when a table that already holds rows changes shape.

See EvolutionAnswers. The default answers nothing, which Razeri reads as nobody has measured this engine — so a change Razeri cannot otherwise prove keeps every value is reported as one that does not, and an operator gets a warning they can read instead of a surprise.

Source

fn supports_returning(&self) -> bool

Whether the platform supports RETURNING / OUTPUT clauses.

Source

fn server_version_query(&self) -> Option<&str>

How to ask a running server its version, or None where this engine has no version a user chooses, or nobody has written the question.

The query answers one row whose first word is the version in the spelling this dialect declares (17, 8.4, 23ai); anything after it is the server’s own words, which a refusal prints. A generated deploy script sends it after login and before the schema, and refuses a server older than the version the bundle was rendered for, naming both. The default is None, and a script that has no question asks none.

Source

fn parse_server_version(&self, answer: &str) -> Option<EngineVersion>

The version in one answer to Self::server_version_query, or None when the answer holds none.

Parameters:

  • answer: the one row the query answered, as text.

The default reads the first word, which is what a deploy script reads too; override it only to read more of the answer than that.

Source

fn supports_cte(&self) -> bool

Whether the platform supports Common Table Expressions (WITH).

Source

fn supports_recursive_cte(&self) -> bool

Whether the platform supports recursive CTEs.

Source

fn supports_window_functions(&self) -> bool

Whether the platform supports window functions.

Source

fn supports_qualify(&self) -> bool

Whether the platform supports QUALIFY clause.

Source

fn supports_groups_frame(&self) -> bool

Whether the platform supports GROUPS frame type in window functions.

Source

fn supports_nulls_ordering(&self) -> bool

Whether the platform supports NULLS FIRST/LAST in ORDER BY.

Source

fn supports_identity(&self) -> bool

Whether the platform supports IDENTITY columns.

Source

fn supports_sequences(&self) -> bool

Whether the platform supports sequences (CREATE SEQUENCE).

Source

fn supports_schemas(&self) -> bool

Whether the platform supports schema namespacing.

Source

fn routine_is_callable_across_schemas(&self) -> bool

Can a routine created in one schema be called from a routine created in another, with nothing else done?

True nearly everywhere, and true by default: a schema on most engines is a namespace, and naming an object in another namespace is all that reaching it takes.

Answer false where a schema is also an owner and reaching across one costs a privilege granted per object. On Oracle a schema IS a user; a routine runs with its owner’s privileges, so a function created in acme_core calling one created in acme_raw compiles with errors — ORA-60535: invalid identifier, measured — however exactly it spells the name.

Razeri reads no answer from it: every layer schema that computes a key holds its own copy of the key function on every engine, so a layer can move to another database without the others, and no key function calls another layer’s whatever this says. The one routine a key function may call outside its own schema is the hash a prerequisite installs once, and where that lives is ReplicationRenderer::sid_prerequisite_home, whose install carries its own grant.

Source

fn supports_bulk_load(&self) -> bool

Whether the platform supports bulk load (COPY, BULK INSERT, etc.).

Source

fn supports_stored_procedures(&self) -> bool

Whether a generated load is written as a stored procedure on this engine.

Razeri asks this before it writes a load as a procedure, and asks SqlRenderer::procedure_form how to spell one. The default is false, and a load is then written as plain statements. A dialect that answers procedure_form with ProcedureForm::Custom answers true here, or its procedures are never asked for; one that answers ProcedureForm::Unsupported leaves this false. The conformance report holds the two to each other.

Source

fn supports_stored_functions(&self) -> bool

Whether the platform stores functions at all — a routine the schema creates and later calls by name, in any spelling.

Nearly every engine razeri reaches has one: CREATE FUNCTION on PostgreSQL, MySQL, MariaDB, Snowflake, SQL Server and Databricks, a MACRO on DuckDB, a lambda CREATE FUNCTION on ClickHouse.

SQLite answers false, and it is the reason this question is asked out loud. SQLite has no CREATE FUNCTION statement in any spelling and no CREATE PROCEDURE either — a function there is registered by the program that opens the database, so nothing written into a schema script can create one. Inheriting the PostgreSQL form wrote CREATE OR REPLACE FUNCTION … LANGUAGE SQL RETURN … into schema.sql, which SQLite answers with near "CREATE": syntax error part way through the script, so the build refuses in front of the script instead.

The default is true, matching Self::supports_table_functions and for the same reason: refusing is the destructive answer, so a refusal fires only where an engine is KNOWN to have no form, never because a dialect forgot to answer. That reverses the conservative-false default this trait uses for flags that add syntax — here false is the unusual claim, and it is made deliberately, one dialect at a time.

Source

fn supports_table_functions(&self) -> bool

Whether the platform has a table-valued function — a routine a query selects FROM, returning columns rather than one value.

Most engines have one in some spelling: RETURNS TABLE(…) on PostgreSQL, Snowflake and Databricks, an inline TVF on SQL Server, a table MACRO on DuckDB, a parameterized view on ClickHouse.

MySQL and MariaDB answer false, and they are the reason this question exists. A stored function there returns exactly one value; there is no clause that returns a relation, in any spelling. Rendering RETURNS TABLE for them produced ERROR 1064 … syntax error in the middle of a schema script (measured on MySQL 8.4 and MariaDB 11.4), so the build refuses in front of the script instead.

The default is true: a refusal fires only where an engine is KNOWN to have no form, never because a dialect forgot to answer.

Source

fn supports_tasks(&self) -> bool

Whether the platform supports tasks / scheduled jobs.

Source

fn supports_partitioning(&self) -> bool

Whether the platform supports native table partitioning.

Source

fn supports_materialized_views(&self) -> bool

Whether the platform supports materialized views.

Source

fn supports_generated_columns(&self) -> bool

Whether the platform supports generated/computed columns.

Source

fn supports_json(&self) -> bool

Whether the platform supports JSONB or native JSON type.

Source

fn supports_unsigned_integers(&self) -> bool

Whether the platform supports unsigned integers natively.

Source

fn supports_arrays(&self) -> bool

Whether the platform supports arrays natively.

Source

fn renders_nested_type_natively(&self, data_type: &DataType) -> bool

Whether this engine has a type of its own for a nested type, or stores it as something else.

A nested column is an array, a map or a record. Some engines have all three; some have one; some have none and hold the value as JSON or as text. Razeri never refuses the column — the design is portable and it is the storage that degrades — but it does say so once, naming the engine, the columns and what each became.

The default is false, which is the honest answer for an engine nobody has answered for: the reader is told what the column became rather than left to assume the type survived.

Parameters:

  • data_type: the nested type in question.
Source

fn compares_nested_values(&self, data_type: &DataType) -> bool

Whether this engine can tell two values of a nested type apart.

Change detection asks whether an arriving value differs from the standing one. For a scalar every engine answers with <>. For a nested value they do not agree: some compare arrays, maps and records directly, some refuse the comparison outright, and some hold the value as text and compare the text. A column this returns false for is left out of change detection and named in a note, rather than compared with an operator the engine will reject.

The default is false — an unanswered engine leaves the column unwatched and says so, which is the failure that can be read.

Parameters:

  • data_type: the nested type in question.
Source

fn groups_nested_values(&self, data_type: &DataType) -> bool

Whether this engine can collapse rows on a nested column — group by it, or select distinct over it.

A load reads its staged rows through one collapsed form so that a row stated twice, identically, is one arriving row rather than a conflict. The plain spelling of that collapse is SELECT DISTINCT over every column, and grouping is what DISTINCT is: an engine that will not group a value of a nested type will not take the column in a DISTINCT either.

It is a separate question from Self::compares_nested_values, and the two engines that answer them differently are the reason it is asked separately: BigQuery compares two records with = and refuses to group on one.

Where the answer is false the collapse is written as a GROUP BY over the other columns with crate::SqlRenderer::render_any_value around this one.

The default is true: SELECT DISTINCT over every column is the plain spelling, and an engine says otherwise only where somebody has found that it refuses.

Asked only of a nested type — an array, a map or a record. The answer for anything else is not read.

Parameters:

  • data_type: the nested type in question.
Source

fn supports_boolean(&self) -> bool

Whether the platform supports native BOOLEAN type (vs BIT).

Source

fn default_schema(&self) -> &'static str

The default schema name for this platform.

Source

fn max_identifier_length(&self) -> usize

Maximum identifier length for this platform.

Source

fn max_columns(&self) -> usize

Maximum number of columns in a table (approximate).

Source

fn max_parameters(&self) -> usize

Maximum query parameters (approximate, 0 = unlimited).

Source

fn decimal_limits(&self) -> Option<DecimalLimits>

The widest exact decimal a column on this platform holds, where it has been measured against the engine.

A type over it is refused by name rather than rendered: the engine would refuse the CREATE TABLE anyway, later and in its own words.

The default is None, which means not measured here, not no limit. An engine with no limit at all says so in its own doc comment and still answers None.

Source

fn text_limits(&self) -> Option<TextLimits>

What a text column on this platform holds, where it has been measured against the engine or read from its documentation (see TextLimits).

The default is None, which means not recorded here, not no limit. Measure the longest varchar(n) your engine creates — one more is refused at CREATE TABLE — and say how it counts it.

Source

fn type_limits(&self) -> Option<TypeLimits>

What this engine’s other columns hold — the digits of a second a timestamp and a time of day keep, and the longest char(n), binary(n) and varbinary(n) (see TypeLimits).

A type over a stated limit is refused by name before anything is rendered. The default is None: nothing is stated, and nothing is refused for this engine, which then refuses the CREATE TABLE later in its own words. Measure each the way the varchar is measured — the most your engine creates, one more refused — and leave None in a field your engine stores with no limit.

Source

fn keeps_a_time_zone(&self) -> bool

Whether this engine has a type that keeps a time zone, for a timestamptz column: a moment that keeps its offset, or one read in the session’s zone. The default is true. An engine with no such type answers false, and a timestamptz column is refused by name rather than stored as a clock time two hours off the moment the file named.

Source

fn supports_function_overloading(&self) -> bool

Whether DROP FUNCTION requires parameter types for overloaded functions.

Source

fn supports_merge_star_syntax(&self) -> bool

Whether the platform supports UPDATE SET * / INSERT * in MERGE.

Source

fn schema_namespace_keyword(&self) -> &'static str

The SQL keyword for the schema-level namespace.

Most platforms use SCHEMA (CREATE SCHEMA IF NOT EXISTS ...). ClickHouse uses DATABASE (CREATE DATABASE IF NOT EXISTS ...).

Source

fn supports_hash_comments(&self) -> bool

Whether the platform supports # as a single-line comment delimiter.

Default: false. A dialect whose engine accepts # as a single-line comment delimiter overrides this.

Source

fn batch_separator(&self) -> Option<&'static str>

Batch separator keyword required between DDL statements in a script.

SQL Server / T-SQL requires GO between CREATE FUNCTION, CREATE VIEW, and CREATE PROCEDURE statements. Other platforms return None and use double-newlines between statements.

Source

fn supports_multi_statement_transactions(&self) -> bool

Whether several statements in a script can be committed or discarded together as one unit.

A generated load is a sequence of writes that only make sense finished. An SCD-2 load, for instance, closes the standing version into _history and then re-opens _current on the new attributes; a process that dies between the two leaves a closed version that _current still holds open, and because _history is keyed on (surrogate, version start), the next load writes the same key again and the engine refuses it. Every load after the crash fails until somebody deletes that row by hand.

Wrapping the load in one transaction is what makes the crash a no-op instead. This flag says whether the platform offers that at all. It defaults to true because a transaction spanning several statements is core SQL and nearly every engine has it; a platform that does not overrides it, and the load script then says plainly that the operator owns the boundary.

Source

fn begin_transaction_keyword(&self) -> &'static str

How this platform opens a transaction.

The ANSI spelling, which MySQL and MariaDB also use. DuckDB, SQLite, SQL Server and Snowflake say BEGIN TRANSACTION; PostgreSQL and Citus say BEGIN.

Source

fn commit_transaction_keyword(&self) -> &'static str

How this platform commits one.

COMMIT everywhere razeri reaches except SQL Server, which spells it COMMIT TRANSACTION.

Source

fn begin_leaves_the_commit_to_the_client(&self) -> bool

Whether this engine’s begin statement leaves the commit to the client.

On most engines begin_transaction_keyword opens a transaction that holds every statement after it until the commit, whatever the client does. On some it only names the transaction — Oracle’s and SAP HANA’s SET TRANSACTION READ WRITE — and a driver that commits each statement on its own still does, so a load run that way keeps what it wrote when it stops part way.

Razeri’s own deploy scripts drive clients whose commit is off. Answer true where the engine needs that of the client, and the bundle tells whoever runs a load another way — a scheduler running the task list — to turn its client’s commit off. The default is false.

Source

fn truncate_commits_implicitly(&self) -> bool

Whether emptying a table with TRUNCATE ends the open transaction.

On some engines TRUNCATE TABLE commits everything before it and cannot be rolled back: MySQL and MariaDB, which treat it as a schema change, and Oracle, SAP HANA, BigQuery and Redshift. A generated load empties its staging table only as its last statement, after every write it makes, so on those engines the clear commits the load with it and each load stands on its own; a load that stops before it leaves nothing behind. Everywhere else TRUNCATE is an ordinary statement inside the transaction.

Source

fn refusal_stops_the_statement(&self) -> bool

Whether a load’s refusal stops the statement that asks it.

A generated load stops itself by building a sentence about the rows it refuses and asking the engine for ExpressionRenderer::render_refusal of it: the engine refuses and quotes the sentence back in its error. Most engines do. MySQL and MariaDB answer the default refusal, a conversion to a number, with a warning and 0, and Spark answers NULL where ANSI mode is off. There the load stops on a second statement instead, in the engine’s own words and without the sentence, so a deploy script reads the refused rows back and prints them itself.

The default is true, the answer of every engine whose refusal stops.

Source

fn delimited_file_reads_null_word_as_missing(&self) -> bool

Whether this engine’s file reader may read a field holding just the word NULL as a missing value rather than as the four letters.

A design names the words that mean “no value” in each file, and a binding that names none calls NULL missing, which every engine then agrees on. A file that keeps NULL as text cannot be read that way here, so the bundle names the file, and a reconciliation does not compare the characters of a text column holding the word. The default is false: the engine keeps the text, as every engine’s own file reader does except MySQL’s and MariaDB’s LOAD DATA.

Source

fn supports_partition_function(&self) -> bool

Whether the platform supports a server-side partition function definable at design time (T-SQL PARTITION FUNCTION, PostgreSQL PARTITION BY, MySQL PARTITION BY, ClickHouse PARTITION BY).

Snowflake / DuckDB return false because micro-partitioning is automatic and not user-definable.

Source

fn supports_partition_split_merge(&self) -> bool

Whether the platform supports adding/removing partition boundaries on a live table (T-SQL ALTER PARTITION FUNCTION SPLIT/MERGE RANGE, PostgreSQL ATTACH/DETACH PARTITION, MySQL REORGANIZE PARTITION).

Source

fn supports_columnstore_index(&self) -> bool

Whether the platform supports a columnstore index (T-SQL clustered/non-clustered columnstore; ClickHouse via MergeTree engine; not natively in PostgreSQL / MySQL).

Source

fn columnstore_is_table_engine(&self) -> bool

Whether the platform realizes a columnstore as a table storage engine rather than an index (MariaDB ENGINE=ColumnStore). This is deliberately distinct from Self::supports_columnstore_index: a design that asks for index: { kind: clustered_columnstore } is honoured on such a platform by selecting the engine, but it must NOT inherit the T-SQL index machinery — the composite-PK demotion to UNIQUE NONCLUSTERED keys off supports_columnstore_index, and an engine-columnstore keeps its ordinary primary key. So the two capabilities are separate axes: “supports columnstore at all” is the disjunction of them.

Source

fn supports_clustering_key(&self) -> bool

Whether the platform has a clustering-key concept distinct from primary key (Snowflake CLUSTER BY, Delta Liquid Clustering / Z-ORDER, ClickHouse ORDER BY).

Source

fn supports_table_compression(&self) -> bool

Whether the platform supports user-controllable table compression (T-SQL DATA_COMPRESSION = PAGE/ROW/COLUMNSTORE, MySQL ROW_FORMAT=COMPRESSED, ClickHouse CODEC(ZSTD), Delta delta.compressionCodec).

Source

fn partition_requires_pk_includes_field(&self) -> bool

Whether the platform requires every column referenced by the partition key to also appear in the table’s PRIMARY KEY (or any UNIQUE constraint).

PostgreSQL enforces this with SQLSTATE 0A000 (“unique constraint on partitioned table must include all partitioning columns”). T-SQL, MySQL, ClickHouse, Snowflake, and DuckDB do not impose this restriction.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§