use super::*;
impl FeatureSet {
pub const fn lexical_conflict(&self) -> Option<LexicalConflict> {
if self.string_literals.double_quoted_strings
&& identifier_quotes_open_with(self.identifier_quotes, '"')
{
return Some(LexicalConflict::DoubleQuoteStringVersusIdentifier);
}
if identifier_quotes_open_with(self.identifier_quotes, '[')
&& (self.expression_syntax.subscript
|| self.expression_syntax.array_constructor
|| self.expression_syntax.collection_literals
|| self.table_expressions.table_json_path)
{
return Some(LexicalConflict::BracketIdentifierVersusArraySyntax);
}
if self.parameters.named_colon
&& (self.expression_syntax.subscript
|| self.expression_syntax.collection_literals
|| self.expression_syntax.semi_structured_access)
{
return Some(LexicalConflict::ColonParameterVersusSliceBound);
}
if self.numeric_literals.money_literals && self.parameters.positional_dollar {
return Some(LexicalConflict::MoneyVersusPositionalDollar);
}
if self.parameters.named_dollar && self.string_literals.dollar_quoted_strings {
return Some(LexicalConflict::NamedDollarParameterVersusDollarQuotedString);
}
if self.comment_syntax.line_comment_hash
&& self
.byte_classes
.has_class(b'#', lex_class::CLASS_IDENTIFIER_START)
{
return Some(LexicalConflict::HashCommentVersusHashIdentifier);
}
if self.hash_bitwise_xor && self.comment_syntax.line_comment_hash {
return Some(LexicalConflict::HashXorOperatorVersusHashComment);
}
if self.expression_syntax.positional_column && self.hash_bitwise_xor {
return Some(LexicalConflict::HashXorOperatorVersusPositionalColumn);
}
if self.expression_syntax.positional_column && self.comment_syntax.line_comment_hash {
return Some(LexicalConflict::HashCommentVersusPositionalColumn);
}
if self.parameters.named_at && self.session_variables.user_variables {
return Some(LexicalConflict::AtNameParameterVersusUserVariable);
}
if self.operator_syntax.containment_operators
&& (self.parameters.named_at || self.session_variables.user_variables)
{
return Some(LexicalConflict::ContainmentOperatorVersusAtName);
}
if self.operator_syntax.jsonb_operators && self.parameters.anonymous_question {
return Some(LexicalConflict::JsonbKeyExistsVersusAnonymousParameter);
}
if self.operator_syntax.jsonb_operators && self.session_variables.system_variables {
return Some(LexicalConflict::JsonbSearchOperatorVersusSystemVariable);
}
if self.operator_syntax.custom_operators
&& (self.parameters.named_at || self.session_variables.user_variables)
{
return Some(LexicalConflict::CustomOperatorVersusAtName);
}
if self.operator_syntax.custom_operators
&& !self.operator_syntax.jsonb_operators
&& self.session_variables.system_variables
{
return Some(LexicalConflict::CustomOperatorVersusSystemVariable);
}
if (self.string_literals.dollar_quoted_strings
|| self.parameters.positional_dollar
|| self.numeric_literals.money_literals)
&& self
.byte_classes
.has_class(b'$', lex_class::CLASS_IDENTIFIER_START)
{
return Some(LexicalConflict::DollarSigilVersusIdentifierByte);
}
if (self.parameters.named_at
|| self.session_variables.user_variables
|| self.session_variables.system_variables)
&& self
.byte_classes
.has_class(b'@', lex_class::CLASS_IDENTIFIER_START)
{
return Some(LexicalConflict::AtSigilVersusIdentifierByte);
}
if self.parameters.named_colon
&& self
.byte_classes
.has_class(b':', lex_class::CLASS_IDENTIFIER_START)
{
return Some(LexicalConflict::ColonSigilVersusIdentifierByte);
}
None
}
pub const fn is_lexically_consistent(&self) -> bool {
self.lexical_conflict().is_none()
}
pub const fn feature_dependencies(&self) -> Option<FeatureDependencyViolation> {
use FeatureDependencyViolation as V;
if self.query_tail_syntax.key_lock_strengths && !self.query_tail_syntax.locking_clauses {
return Some(V::KeyLockStrengthsWithoutLockingClauses);
}
if self.query_tail_syntax.stacked_locking_clauses && !self.query_tail_syntax.locking_clauses
{
return Some(V::StackedLockingClausesWithoutLockingClauses);
}
if self.table_factor_syntax.unnest_with_offset && !self.table_factor_syntax.unnest {
return Some(V::UnnestWithOffsetWithoutUnnest);
}
if self.expression_syntax.slice_step && !self.expression_syntax.subscript {
return Some(V::SliceStepWithoutSubscript);
}
if self.expression_syntax.multidim_array_literals
&& !self.expression_syntax.array_constructor
{
return Some(V::MultidimArrayLiteralsWithoutArrayConstructor);
}
if self.operator_syntax.quantified_comparison_lists
&& !self.operator_syntax.quantified_comparisons
{
return Some(V::QuantifiedComparisonListsWithoutQuantifiedComparisons);
}
if self.operator_syntax.quantified_arbitrary_operator
&& !self.operator_syntax.quantified_comparisons
{
return Some(V::QuantifiedArbitraryOperatorWithoutQuantifiedComparisons);
}
if self.operator_syntax.lambda_expressions && !self.operator_syntax.json_arrow_operators {
return Some(V::LambdaExpressionsWithoutJsonArrowOperators);
}
if self.mutation_syntax.cte_before_merge && !self.mutation_syntax.merge {
return Some(V::CteBeforeMergeWithoutMerge);
}
if self.mutation_syntax.merge_when_not_matched_by && !self.mutation_syntax.merge {
return Some(V::MergeWhenNotMatchedByWithoutMerge);
}
if self.mutation_syntax.merge_insert_default_values && !self.mutation_syntax.merge {
return Some(V::MergeInsertDefaultValuesWithoutMerge);
}
if self.mutation_syntax.merge_insert_overriding && !self.mutation_syntax.merge {
return Some(V::MergeInsertOverridingWithoutMerge);
}
if self.index_alter_syntax.alter_existence_guards
&& !self.index_alter_syntax.alter_table_extended
{
return Some(V::AlterExistenceGuardsWithoutAlterTableExtended);
}
if self.index_alter_syntax.alter_column_set_data_type
&& !self.index_alter_syntax.alter_table_extended
{
return Some(V::AlterColumnSetDataTypeWithoutAlterTableExtended);
}
if self.maintenance_syntax.checkpoint_database && !self.maintenance_syntax.checkpoint {
return Some(V::CheckpointDatabaseWithoutCheckpoint);
}
if self.maintenance_syntax.analyze_columns && !self.maintenance_syntax.analyze {
return Some(V::AnalyzeColumnsWithoutAnalyze);
}
if self.utility_syntax.load_bare_name && !self.utility_syntax.load_extension {
return Some(V::LoadBareNameWithoutLoadExtension);
}
if self.utility_syntax.call_bare_name && !self.utility_syntax.call {
return Some(V::CallBareNameWithoutCall);
}
if self.utility_syntax.detach_if_exists && !self.utility_syntax.attach {
return Some(V::DetachIfExistsWithoutAttach);
}
if self.utility_syntax.use_qualified_name && !self.utility_syntax.use_statement {
return Some(V::UseQualifiedNameWithoutUseStatement);
}
if self.utility_syntax.use_string_literal_name && !self.utility_syntax.use_statement {
return Some(V::UseStringLiteralNameWithoutUseStatement);
}
if self.access_control_syntax.access_control_extended_objects
&& !self.access_control_syntax.access_control
{
return Some(V::AccessControlExtendedObjectsWithoutAccessControl);
}
if self.access_control_syntax.access_control_account_grants
&& !self.access_control_syntax.access_control
{
return Some(V::AccountGrantsWithoutAccessControl);
}
if self.utility_syntax.prepare_typed_parameters && !self.utility_syntax.prepared_statements
{
return Some(V::PrepareTypedParametersWithoutPreparedStatements);
}
None
}
pub const fn has_satisfied_feature_dependencies(&self) -> bool {
self.feature_dependencies().is_none()
}
pub fn without_dangling_dependents(&self) -> FeatureSet {
use FeatureDependencyViolation as V;
let mut features = self.clone();
while let Some(violation) = features.feature_dependencies() {
match violation {
V::KeyLockStrengthsWithoutLockingClauses => {
features.query_tail_syntax.key_lock_strengths = false;
}
V::StackedLockingClausesWithoutLockingClauses => {
features.query_tail_syntax.stacked_locking_clauses = false;
}
V::UnnestWithOffsetWithoutUnnest => {
features.table_factor_syntax.unnest_with_offset = false;
}
V::SliceStepWithoutSubscript => {
features.expression_syntax.slice_step = false;
}
V::MultidimArrayLiteralsWithoutArrayConstructor => {
features.expression_syntax.multidim_array_literals = false;
}
V::QuantifiedComparisonListsWithoutQuantifiedComparisons => {
features.operator_syntax.quantified_comparison_lists = false;
}
V::QuantifiedArbitraryOperatorWithoutQuantifiedComparisons => {
features.operator_syntax.quantified_arbitrary_operator = false;
}
V::LambdaExpressionsWithoutJsonArrowOperators => {
features.operator_syntax.lambda_expressions = false;
}
V::CteBeforeMergeWithoutMerge => {
features.mutation_syntax.cte_before_merge = false;
}
V::MergeWhenNotMatchedByWithoutMerge => {
features.mutation_syntax.merge_when_not_matched_by = false;
}
V::MergeInsertDefaultValuesWithoutMerge => {
features.mutation_syntax.merge_insert_default_values = false;
}
V::MergeInsertOverridingWithoutMerge => {
features.mutation_syntax.merge_insert_overriding = false;
}
V::AlterExistenceGuardsWithoutAlterTableExtended => {
features.index_alter_syntax.alter_existence_guards = false;
}
V::AlterColumnSetDataTypeWithoutAlterTableExtended => {
features.index_alter_syntax.alter_column_set_data_type = false;
}
V::CheckpointDatabaseWithoutCheckpoint => {
features.maintenance_syntax.checkpoint_database = false;
}
V::AnalyzeColumnsWithoutAnalyze => {
features.maintenance_syntax.analyze_columns = false;
}
V::LoadBareNameWithoutLoadExtension => {
features.utility_syntax.load_bare_name = false;
}
V::CallBareNameWithoutCall => {
features.utility_syntax.call_bare_name = false;
}
V::DetachIfExistsWithoutAttach => {
features.utility_syntax.detach_if_exists = false;
}
V::UseQualifiedNameWithoutUseStatement => {
features.utility_syntax.use_qualified_name = false;
}
V::UseStringLiteralNameWithoutUseStatement => {
features.utility_syntax.use_string_literal_name = false;
}
V::AccessControlExtendedObjectsWithoutAccessControl => {
features
.access_control_syntax
.access_control_extended_objects = false;
}
V::AccountGrantsWithoutAccessControl => {
features.access_control_syntax.access_control_account_grants = false;
}
V::PrepareTypedParametersWithoutPreparedStatements => {
features.utility_syntax.prepare_typed_parameters = false;
}
}
}
features
}
pub const fn grammar_conflict(&self) -> Option<GrammarConflict> {
use GrammarConflict as G;
if (self.select_syntax.prefix_colon_alias || self.table_expressions.prefix_colon_alias)
&& self.expression_syntax.semi_structured_access
{
return Some(G::PrefixColonAliasVersusSemiStructuredAccess);
}
if self.utility_syntax.do_statement && self.utility_syntax.do_expression_list {
return Some(G::DoStatementVersusDoExpressionList);
}
if self.utility_syntax.prepared_statements && self.utility_syntax.prepared_statements_from {
return Some(G::PreparedStatementsVersusPreparedStatementsFrom);
}
if self.access_control_syntax.access_control_account_grants
&& self.access_control_syntax.access_control_extended_objects
{
return Some(G::AccountGrantsVersusExtendedObjects);
}
None
}
pub const fn has_no_grammar_conflict(&self) -> bool {
self.grammar_conflict().is_none()
}
pub const fn try_with(&self, delta: FeatureDelta) -> Result<Self, LexicalConflict> {
let candidate = self.with(delta);
match candidate.lexical_conflict() {
Some(conflict) => Err(conflict),
None => Ok(candidate),
}
}
}
const fn identifier_quotes_open_with(quotes: &[IdentifierQuote], open: char) -> bool {
let mut index = 0;
while index < quotes.len() {
if quotes[index].open() == open {
return true;
}
index += 1;
}
false
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum LexicalConflict {
DoubleQuoteStringVersusIdentifier,
BracketIdentifierVersusArraySyntax,
MoneyVersusPositionalDollar,
AtNameParameterVersusUserVariable,
NamedDollarParameterVersusDollarQuotedString,
HashCommentVersusHashIdentifier,
HashXorOperatorVersusHashComment,
HashXorOperatorVersusPositionalColumn,
HashCommentVersusPositionalColumn,
ColonParameterVersusSliceBound,
ContainmentOperatorVersusAtName,
JsonbKeyExistsVersusAnonymousParameter,
JsonbSearchOperatorVersusSystemVariable,
CustomOperatorVersusAtName,
CustomOperatorVersusSystemVariable,
DollarSigilVersusIdentifierByte,
AtSigilVersusIdentifierByte,
ColonSigilVersusIdentifierByte,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FeatureDependencyViolation {
KeyLockStrengthsWithoutLockingClauses,
StackedLockingClausesWithoutLockingClauses,
UnnestWithOffsetWithoutUnnest,
SliceStepWithoutSubscript,
MultidimArrayLiteralsWithoutArrayConstructor,
QuantifiedComparisonListsWithoutQuantifiedComparisons,
QuantifiedArbitraryOperatorWithoutQuantifiedComparisons,
LambdaExpressionsWithoutJsonArrowOperators,
CteBeforeMergeWithoutMerge,
MergeWhenNotMatchedByWithoutMerge,
MergeInsertDefaultValuesWithoutMerge,
MergeInsertOverridingWithoutMerge,
AlterExistenceGuardsWithoutAlterTableExtended,
AlterColumnSetDataTypeWithoutAlterTableExtended,
CheckpointDatabaseWithoutCheckpoint,
AnalyzeColumnsWithoutAnalyze,
LoadBareNameWithoutLoadExtension,
CallBareNameWithoutCall,
DetachIfExistsWithoutAttach,
UseQualifiedNameWithoutUseStatement,
UseStringLiteralNameWithoutUseStatement,
AccessControlExtendedObjectsWithoutAccessControl,
AccountGrantsWithoutAccessControl,
PrepareTypedParametersWithoutPreparedStatements,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum GrammarConflict {
PrefixColonAliasVersusSemiStructuredAccess,
DoStatementVersusDoExpressionList,
PreparedStatementsVersusPreparedStatementsFrom,
AccountGrantsVersusExtendedObjects,
}
#[cfg(test)]
mod tests {
use crate::dialect::lex_class::CLASS_IDENTIFIER_START;
use crate::dialect::*;
#[test]
fn lexical_conflict_flags_a_sigil_byte_marked_identifier_start() {
let dollar = FeatureSet::ANSI.with(
FeatureDelta::EMPTY
.parameters(ParameterSyntax {
positional_dollar: true,
..ParameterSyntax::ANSI
})
.byte_classes(
FeatureSet::ANSI
.byte_classes
.with_class(b'$', CLASS_IDENTIFIER_START),
),
);
assert_eq!(
dollar.lexical_conflict(),
Some(LexicalConflict::DollarSigilVersusIdentifierByte),
);
let at = FeatureSet::ANSI.with(
FeatureDelta::EMPTY
.parameters(ParameterSyntax {
named_at: true,
..ParameterSyntax::ANSI
})
.byte_classes(
FeatureSet::ANSI
.byte_classes
.with_class(b'@', CLASS_IDENTIFIER_START),
),
);
assert_eq!(
at.lexical_conflict(),
Some(LexicalConflict::AtSigilVersusIdentifierByte),
);
let colon = FeatureSet::ANSI.with(
FeatureDelta::EMPTY
.parameters(ParameterSyntax {
named_colon: true,
..ParameterSyntax::ANSI
})
.byte_classes(
FeatureSet::ANSI
.byte_classes
.with_class(b':', CLASS_IDENTIFIER_START),
),
);
assert_eq!(
colon.lexical_conflict(),
Some(LexicalConflict::ColonSigilVersusIdentifierByte),
);
let dollar_identifier_only = FeatureSet::ANSI.with(
FeatureDelta::EMPTY.byte_classes(
FeatureSet::ANSI
.byte_classes
.with_class(b'$', CLASS_IDENTIFIER_START),
),
);
assert_eq!(dollar_identifier_only.lexical_conflict(), None);
}
#[test]
fn positional_column_conflicts_on_the_hash_trigger() {
let with_xor = FeatureSet::DUCKDB.with(FeatureDelta::EMPTY.hash_bitwise_xor(true));
assert_eq!(
with_xor.lexical_conflict(),
Some(LexicalConflict::HashXorOperatorVersusPositionalColumn),
);
let with_comment =
FeatureSet::DUCKDB.with(FeatureDelta::EMPTY.comment_syntax(CommentSyntax {
line_comment_hash: true,
..FeatureSet::DUCKDB.comment_syntax
}));
assert_eq!(
with_comment.lexical_conflict(),
Some(LexicalConflict::HashCommentVersusPositionalColumn),
);
assert_eq!(FeatureSet::DUCKDB.lexical_conflict(), None);
}
#[test]
fn every_shipped_preset_satisfies_its_feature_dependencies() {
for preset in [
FeatureSet::ANSI,
FeatureSet::POSTGRES,
FeatureSet::MYSQL,
FeatureSet::SQLITE,
FeatureSet::DUCKDB,
FeatureSet::BIGQUERY,
FeatureSet::HIVE,
FeatureSet::CLICKHOUSE,
FeatureSet::DATABRICKS,
FeatureSet::MSSQL,
FeatureSet::SNOWFLAKE,
FeatureSet::REDSHIFT,
FeatureSet::LENIENT,
] {
assert_eq!(preset.feature_dependencies(), None);
assert!(preset.has_satisfied_feature_dependencies());
}
}
#[test]
fn feature_dependencies_detects_each_unsatisfied_dependency() {
use FeatureDependencyViolation as V;
let key_lock =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.query_tail_syntax(QueryTailSyntax {
locking_clauses: false,
key_lock_strengths: true,
stacked_locking_clauses: false,
..FeatureSet::ANSI.query_tail_syntax
}));
assert_eq!(
key_lock.feature_dependencies(),
Some(V::KeyLockStrengthsWithoutLockingClauses),
);
let stacked =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.query_tail_syntax(QueryTailSyntax {
locking_clauses: false,
key_lock_strengths: false,
stacked_locking_clauses: true,
..FeatureSet::ANSI.query_tail_syntax
}));
assert_eq!(
stacked.feature_dependencies(),
Some(V::StackedLockingClausesWithoutLockingClauses),
);
let unnest_offset =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.table_factor_syntax(TableFactorSyntax {
unnest: false,
unnest_with_offset: true,
..FeatureSet::ANSI.table_factor_syntax
}));
assert_eq!(
unnest_offset.feature_dependencies(),
Some(V::UnnestWithOffsetWithoutUnnest),
);
let slice =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.expression_syntax(ExpressionSyntax {
subscript: false,
slice_step: true,
..FeatureSet::ANSI.expression_syntax
}));
assert_eq!(
slice.feature_dependencies(),
Some(V::SliceStepWithoutSubscript)
);
let multidim =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.expression_syntax(ExpressionSyntax {
array_constructor: false,
multidim_array_literals: true,
..FeatureSet::ANSI.expression_syntax
}));
assert_eq!(
multidim.feature_dependencies(),
Some(V::MultidimArrayLiteralsWithoutArrayConstructor),
);
let quant_lists =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.operator_syntax(OperatorSyntax {
quantified_comparisons: false,
quantified_comparison_lists: true,
quantified_arbitrary_operator: false,
..FeatureSet::ANSI.operator_syntax
}));
assert_eq!(
quant_lists.feature_dependencies(),
Some(V::QuantifiedComparisonListsWithoutQuantifiedComparisons),
);
let quant_arbitrary =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.operator_syntax(OperatorSyntax {
quantified_comparisons: false,
quantified_comparison_lists: false,
quantified_arbitrary_operator: true,
..FeatureSet::ANSI.operator_syntax
}));
assert_eq!(
quant_arbitrary.feature_dependencies(),
Some(V::QuantifiedArbitraryOperatorWithoutQuantifiedComparisons),
);
let lambda = FeatureSet::ANSI.with(FeatureDelta::EMPTY.operator_syntax(OperatorSyntax {
json_arrow_operators: false,
lambda_expressions: true,
..FeatureSet::ANSI.operator_syntax
}));
assert_eq!(
lambda.feature_dependencies(),
Some(V::LambdaExpressionsWithoutJsonArrowOperators),
);
let merge_base = MutationSyntax {
merge: false,
cte_before_merge: false,
merge_when_not_matched_by: false,
merge_insert_default_values: false,
merge_insert_overriding: false,
merge_update_set_star: false,
merge_insert_star_by_name: false,
merge_error_action: false,
..FeatureSet::ANSI.mutation_syntax
};
let cte_merge =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.mutation_syntax(MutationSyntax {
cte_before_merge: true,
..merge_base
}));
assert_eq!(
cte_merge.feature_dependencies(),
Some(V::CteBeforeMergeWithoutMerge),
);
let when_not_matched =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.mutation_syntax(MutationSyntax {
merge_when_not_matched_by: true,
..merge_base
}));
assert_eq!(
when_not_matched.feature_dependencies(),
Some(V::MergeWhenNotMatchedByWithoutMerge),
);
let insert_defaults =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.mutation_syntax(MutationSyntax {
merge_insert_default_values: true,
..merge_base
}));
assert_eq!(
insert_defaults.feature_dependencies(),
Some(V::MergeInsertDefaultValuesWithoutMerge),
);
let insert_overriding =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.mutation_syntax(MutationSyntax {
merge_insert_overriding: true,
merge_update_set_star: false,
merge_insert_star_by_name: false,
merge_error_action: false,
..merge_base
}));
assert_eq!(
insert_overriding.feature_dependencies(),
Some(V::MergeInsertOverridingWithoutMerge),
);
let alter_guards =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.index_alter_syntax(IndexAlterSyntax {
alter_table_extended: false,
alter_existence_guards: true,
alter_column_set_data_type: false,
..FeatureSet::ANSI.index_alter_syntax
}));
assert_eq!(
alter_guards.feature_dependencies(),
Some(V::AlterExistenceGuardsWithoutAlterTableExtended),
);
let alter_set_type =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.index_alter_syntax(IndexAlterSyntax {
alter_table_extended: false,
alter_existence_guards: false,
alter_column_set_data_type: true,
..FeatureSet::ANSI.index_alter_syntax
}));
assert_eq!(
alter_set_type.feature_dependencies(),
Some(V::AlterColumnSetDataTypeWithoutAlterTableExtended),
);
let checkpoint_db =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.maintenance_syntax(MaintenanceSyntax {
checkpoint: false,
checkpoint_database: true,
..FeatureSet::ANSI.maintenance_syntax
}));
assert_eq!(
checkpoint_db.feature_dependencies(),
Some(V::CheckpointDatabaseWithoutCheckpoint),
);
let analyze_cols =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.maintenance_syntax(MaintenanceSyntax {
analyze: false,
analyze_columns: true,
..FeatureSet::ANSI.maintenance_syntax
}));
assert_eq!(
analyze_cols.feature_dependencies(),
Some(V::AnalyzeColumnsWithoutAnalyze),
);
let load_bare = FeatureSet::ANSI.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
load_extension: false,
load_bare_name: true,
..FeatureSet::ANSI.utility_syntax
}));
assert_eq!(
load_bare.feature_dependencies(),
Some(V::LoadBareNameWithoutLoadExtension),
);
let call_bare = FeatureSet::ANSI.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
call: false,
call_bare_name: true,
..FeatureSet::ANSI.utility_syntax
}));
assert_eq!(
call_bare.feature_dependencies(),
Some(V::CallBareNameWithoutCall),
);
let detach = FeatureSet::ANSI.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
attach: false,
detach_if_exists: true,
..FeatureSet::ANSI.utility_syntax
}));
assert_eq!(
detach.feature_dependencies(),
Some(V::DetachIfExistsWithoutAttach),
);
let use_qualified =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
use_statement: false,
use_qualified_name: true,
..FeatureSet::ANSI.utility_syntax
}));
assert_eq!(
use_qualified.feature_dependencies(),
Some(V::UseQualifiedNameWithoutUseStatement),
);
let use_string = FeatureSet::ANSI.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
use_statement: false,
use_string_literal_name: true,
..FeatureSet::ANSI.utility_syntax
}));
assert_eq!(
use_string.feature_dependencies(),
Some(V::UseStringLiteralNameWithoutUseStatement),
);
let extended_ac = FeatureSet::ANSI.with(FeatureDelta::EMPTY.access_control_syntax(
AccessControlSyntax {
access_control: false,
access_control_extended_objects: true,
user_role_management: false,
access_control_account_grants: false,
alter_role_rename: false,
},
));
assert_eq!(
extended_ac.feature_dependencies(),
Some(V::AccessControlExtendedObjectsWithoutAccessControl),
);
let account_grants = FeatureSet::ANSI.with(FeatureDelta::EMPTY.access_control_syntax(
AccessControlSyntax {
access_control: false,
access_control_extended_objects: false,
user_role_management: false,
access_control_account_grants: true,
alter_role_rename: false,
},
));
assert_eq!(
account_grants.feature_dependencies(),
Some(V::AccountGrantsWithoutAccessControl),
);
let typed_params =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
prepared_statements: false,
prepare_typed_parameters: true,
..FeatureSet::ANSI.utility_syntax
}));
assert_eq!(
typed_params.feature_dependencies(),
Some(V::PrepareTypedParametersWithoutPreparedStatements),
);
}
#[test]
fn feature_dependencies_and_lexical_conflict_stay_mece() {
let dep_only =
FeatureSet::ANSI.with(FeatureDelta::EMPTY.expression_syntax(ExpressionSyntax {
subscript: false,
slice_step: true,
..FeatureSet::ANSI.expression_syntax
}));
assert_eq!(
dep_only.feature_dependencies(),
Some(FeatureDependencyViolation::SliceStepWithoutSubscript),
);
assert_eq!(dep_only.lexical_conflict(), None);
}
#[test]
fn without_dangling_dependents_clears_only_the_inert_refinements() {
let dangling =
FeatureSet::POSTGRES.with(FeatureDelta::EMPTY.mutation_syntax(MutationSyntax {
merge: false,
..FeatureSet::POSTGRES.mutation_syntax
}));
assert!(dangling.feature_dependencies().is_some());
let cleaned = dangling.without_dangling_dependents();
assert_eq!(cleaned.feature_dependencies(), None);
assert!(cleaned.has_satisfied_feature_dependencies());
assert!(!cleaned.mutation_syntax.cte_before_merge);
assert!(!cleaned.mutation_syntax.merge_when_not_matched_by);
assert_eq!(
cleaned.mutation_syntax.merge,
dangling.mutation_syntax.merge
);
assert_eq!(
FeatureSet::POSTGRES.without_dangling_dependents(),
FeatureSet::POSTGRES,
);
}
#[test]
fn every_shipped_preset_has_no_grammar_conflict() {
for preset in [
FeatureSet::ANSI,
FeatureSet::POSTGRES,
FeatureSet::MYSQL,
FeatureSet::SQLITE,
FeatureSet::DUCKDB,
FeatureSet::BIGQUERY,
FeatureSet::HIVE,
FeatureSet::CLICKHOUSE,
FeatureSet::DATABRICKS,
FeatureSet::MSSQL,
FeatureSet::SNOWFLAKE,
FeatureSet::REDSHIFT,
FeatureSet::LENIENT,
] {
assert_eq!(preset.grammar_conflict(), None);
assert!(preset.has_no_grammar_conflict());
}
}
#[test]
fn grammar_conflict_detects_prefix_colon_versus_semi_structured() {
let both =
FeatureSet::DUCKDB.with(FeatureDelta::EMPTY.expression_syntax(ExpressionSyntax {
semi_structured_access: true,
..FeatureSet::DUCKDB.expression_syntax
}));
assert_eq!(
both.grammar_conflict(),
Some(GrammarConflict::PrefixColonAliasVersusSemiStructuredAccess),
);
assert!(!both.has_no_grammar_conflict());
assert_eq!(FeatureSet::DUCKDB.grammar_conflict(), None);
assert_eq!(FeatureSet::SNOWFLAKE.grammar_conflict(), None);
}
#[test]
fn grammar_conflict_stays_mece_with_the_lexical_and_dependency_siblings() {
let grammar_only =
FeatureSet::DUCKDB.with(FeatureDelta::EMPTY.expression_syntax(ExpressionSyntax {
semi_structured_access: true,
..FeatureSet::DUCKDB.expression_syntax
}));
assert_eq!(
grammar_only.grammar_conflict(),
Some(GrammarConflict::PrefixColonAliasVersusSemiStructuredAccess),
);
assert_eq!(grammar_only.lexical_conflict(), None);
assert_eq!(grammar_only.feature_dependencies(), None);
}
#[test]
fn grammar_conflict_detects_do_statement_versus_do_expression_list() {
let both = FeatureSet::MYSQL.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
do_statement: true,
..FeatureSet::MYSQL.utility_syntax
}));
assert_eq!(
both.grammar_conflict(),
Some(GrammarConflict::DoStatementVersusDoExpressionList),
);
assert!(!both.has_no_grammar_conflict());
assert_eq!(FeatureSet::MYSQL.grammar_conflict(), None);
assert_eq!(FeatureSet::POSTGRES.grammar_conflict(), None);
}
#[test]
fn grammar_conflict_detects_prepared_statements_versus_prepared_statements_from() {
let both = FeatureSet::DUCKDB.with(FeatureDelta::EMPTY.utility_syntax(UtilitySyntax {
prepared_statements_from: true,
..FeatureSet::DUCKDB.utility_syntax
}));
assert_eq!(
both.grammar_conflict(),
Some(GrammarConflict::PreparedStatementsVersusPreparedStatementsFrom),
);
assert!(!both.has_no_grammar_conflict());
assert_eq!(FeatureSet::DUCKDB.grammar_conflict(), None);
assert_eq!(FeatureSet::MYSQL.grammar_conflict(), None);
}
#[test]
fn grammar_conflict_detects_account_grants_versus_extended_objects() {
let both = FeatureSet::MYSQL.with(FeatureDelta::EMPTY.access_control_syntax(
AccessControlSyntax {
access_control_extended_objects: true,
..FeatureSet::MYSQL.access_control_syntax
},
));
assert_eq!(
both.grammar_conflict(),
Some(GrammarConflict::AccountGrantsVersusExtendedObjects),
);
assert!(!both.has_no_grammar_conflict());
assert_eq!(both.feature_dependencies(), None);
assert_eq!(FeatureSet::MYSQL.grammar_conflict(), None);
assert_eq!(FeatureSet::POSTGRES.grammar_conflict(), None);
}
}