diff --git a/psl/psl-core/src/common.rs b/psl/psl-core/src/common.rs index 1734ce6619a5..69ca424e4870 100644 --- a/psl/psl-core/src/common.rs +++ b/psl/psl-core/src/common.rs @@ -2,4 +2,4 @@ mod preview_features; -pub use self::preview_features::{FeatureMap, PreviewFeature, PreviewFeatures, ALL_PREVIEW_FEATURES}; +pub use self::preview_features::{FeatureMapWithProvider, PreviewFeature, PreviewFeatures, ALL_PREVIEW_FEATURES}; diff --git a/psl/psl-core/src/common/preview_features.rs b/psl/psl-core/src/common/preview_features.rs index ea9b0eceea81..6e0fc3e40345 100644 --- a/psl/psl-core/src/common/preview_features.rs +++ b/psl/psl-core/src/common/preview_features.rs @@ -86,12 +86,11 @@ features!( ); /// Generator preview features (alphabetically sorted) -pub const ALL_PREVIEW_FEATURES: FeatureMap = FeatureMap { +const PREVIEW_FEATURES: FeatureMap = FeatureMap { active: enumflags2::make_bitflags!(PreviewFeature::{ Deno | DriverAdapters - | FullTextIndex - | FullTextSearch + | FullTextSearch // it's actually GA for "mysql" only! | Metrics | MultiSchema | NativeDistinct @@ -117,6 +116,7 @@ pub const ALL_PREVIEW_FEATURES: FeatureMap = FeatureMap { | FieldReference | FilteredRelationCount | FilterJson + | FullTextIndex | GroupBy | ImprovedQueryRaw | InteractiveTransactions @@ -152,21 +152,46 @@ pub struct FeatureMap { hidden: PreviewFeatures, } -impl FeatureMap { - pub const fn active_features(&self) -> PreviewFeatures { - self.active +#[derive(Debug)] +pub struct FeatureMapWithProvider<'a> { + provider: &'a str, + feature_map: FeatureMap, +} + +/// The default feature map with an unknown provider. +/// This is used for convenience in `prisma/language-tools`, which needs the list of all available preview features +/// before a provider is necessarily known. +pub const ALL_PREVIEW_FEATURES: FeatureMapWithProvider<'static> = FeatureMapWithProvider::new(""); + +impl<'a> FeatureMapWithProvider<'a> { + pub const fn new(connector_provider: &'a str) -> FeatureMapWithProvider<'a> { + Self { + provider: connector_provider, + feature_map: PREVIEW_FEATURES, + } + } + + pub fn active_features(&self) -> PreviewFeatures { + if matches!(self.provider, "mysql") { + self.feature_map.active & !enumflags2::BitFlags::from_flag(PreviewFeature::FullTextSearch) + } else { + self.feature_map.active + } } pub const fn hidden_features(&self) -> PreviewFeatures { - self.hidden + self.feature_map.hidden } pub(crate) fn is_valid(&self, flag: PreviewFeature) -> bool { - (self.active | self.hidden).contains(flag) + (self.active_features() | self.feature_map.hidden).contains(flag) } pub(crate) fn is_deprecated(&self, flag: PreviewFeature) -> bool { - self.deprecated.contains(flag) + if matches!((self.provider, flag), ("mysql", PreviewFeature::FullTextSearch)) { + return true; + } + self.feature_map.deprecated.contains(flag) } } diff --git a/psl/psl-core/src/lib.rs b/psl/psl-core/src/lib.rs index 304ef3cc5cfe..6aed317aa929 100644 --- a/psl/psl-core/src/lib.rs +++ b/psl/psl-core/src/lib.rs @@ -18,7 +18,7 @@ mod validate; use std::sync::Arc; pub use crate::{ - common::{PreviewFeature, PreviewFeatures, ALL_PREVIEW_FEATURES}, + common::{FeatureMapWithProvider, PreviewFeature, PreviewFeatures, ALL_PREVIEW_FEATURES}, configuration::{ Configuration, Datasource, DatasourceConnectorData, Generator, GeneratorConfigValue, StringFromEnvVar, }, @@ -171,8 +171,18 @@ fn validate_configuration( diagnostics: &mut Diagnostics, connectors: ConnectorRegistry<'_>, ) -> Configuration { - let generators = generator_loader::load_generators_from_ast(schema_ast, diagnostics); let datasources = datasource_loader::load_datasources_from_ast(schema_ast, diagnostics, connectors); + // We need to know the active provider to determine which features are active. + // This was originally introduced because the `fullTextSearch` preview feature will hit GA stage + // one connector at a time (Prisma 6 GAs it for MySQL, other connectors may follow in future releases). + let feature_map_with_provider = datasources + .first() + .map(|ds| ds.active_provider) + .map(FeatureMapWithProvider::new) + .unwrap_or_else(|| ALL_PREVIEW_FEATURES); + + let generators = generator_loader::load_generators_from_ast(schema_ast, diagnostics, &feature_map_with_provider); + Configuration::new(generators, datasources, diagnostics.warnings().to_owned()) } diff --git a/psl/psl-core/src/validate/generator_loader.rs b/psl/psl-core/src/validate/generator_loader.rs index ecd1ae1975c1..304010860de9 100644 --- a/psl/psl-core/src/validate/generator_loader.rs +++ b/psl/psl-core/src/validate/generator_loader.rs @@ -1,6 +1,6 @@ use crate::{ ast::WithSpan, - common::{FeatureMap, PreviewFeature, ALL_PREVIEW_FEATURES}, + common::{FeatureMapWithProvider, PreviewFeature}, configuration::{Generator, GeneratorConfigValue, StringFromEnvVar}, diagnostics::*, }; @@ -22,11 +22,15 @@ const ENGINE_TYPE_KEY: &str = "engineType"; const FIRST_CLASS_PROPERTIES: &[&str] = &[PROVIDER_KEY, OUTPUT_KEY, BINARY_TARGETS_KEY, PREVIEW_FEATURES_KEY]; /// Load and validate Generators defined in an AST. -pub(crate) fn load_generators_from_ast(ast_schema: &ast::SchemaAst, diagnostics: &mut Diagnostics) -> Vec { +pub(crate) fn load_generators_from_ast( + ast_schema: &ast::SchemaAst, + diagnostics: &mut Diagnostics, + feature_map_with_provider: &FeatureMapWithProvider<'_>, +) -> Vec { let mut generators: Vec = Vec::new(); for gen in ast_schema.generators() { - if let Some(generator) = lift_generator(gen, diagnostics) { + if let Some(generator) = lift_generator(gen, diagnostics, feature_map_with_provider) { generators.push(generator); } } @@ -34,7 +38,11 @@ pub(crate) fn load_generators_from_ast(ast_schema: &ast::SchemaAst, diagnostics: generators } -fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagnostics) -> Option { +fn lift_generator( + ast_generator: &ast::GeneratorConfig, + diagnostics: &mut Diagnostics, + feature_map_with_provider: &FeatureMapWithProvider<'_>, +) -> Option { let generator_name = ast_generator.name.name.as_str(); let args: HashMap<_, &Expression> = ast_generator .properties @@ -65,6 +73,7 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno } } + // E.g., "prisma-client-js" let provider = match args.get(PROVIDER_KEY) { Some(val) => StringFromEnvVar::coerce(val, diagnostics)?, None => { @@ -92,7 +101,7 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno let preview_features = args .get(PREVIEW_FEATURES_KEY) .and_then(|v| coerce_array(v, &coerce::string, diagnostics).map(|arr| (arr, v.span()))) - .map(|(arr, span)| parse_and_validate_preview_features(arr, &ALL_PREVIEW_FEATURES, span, diagnostics)); + .map(|(arr, span)| parse_and_validate_preview_features(arr, feature_map_with_provider, span, diagnostics)); for prop in &ast_generator.properties { let is_first_class_prop = FIRST_CLASS_PROPERTIES.iter().any(|k| *k == prop.name()); @@ -130,7 +139,7 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno fn parse_and_validate_preview_features( preview_features: Vec<&str>, - feature_map: &FeatureMap, + feature_map_with_provider: &FeatureMapWithProvider<'_>, span: ast::Span, diagnostics: &mut Diagnostics, ) -> BitFlags { @@ -139,15 +148,19 @@ fn parse_and_validate_preview_features( for feature_str in preview_features { let feature_opt = PreviewFeature::parse_opt(feature_str); match feature_opt { - Some(feature) if feature_map.is_deprecated(feature) => { + Some(feature) if feature_map_with_provider.is_deprecated(feature) => { features |= feature; diagnostics.push_warning(DatamodelWarning::new_feature_deprecated(feature_str, span)); } - Some(feature) if !feature_map.is_valid(feature) => { + Some(feature) if !feature_map_with_provider.is_valid(feature) => { diagnostics.push_error(DatamodelError::new_preview_feature_not_known_error( feature_str, - feature_map.active_features().iter().map(|pf| pf.to_string()).join(", "), + feature_map_with_provider + .active_features() + .iter() + .map(|pf| pf.to_string()) + .join(", "), span, )) } @@ -156,7 +169,11 @@ fn parse_and_validate_preview_features( None => diagnostics.push_error(DatamodelError::new_preview_feature_not_known_error( feature_str, - feature_map.active_features().iter().map(|pf| pf.to_string()).join(", "), + feature_map_with_provider + .active_features() + .iter() + .map(|pf| pf.to_string()) + .join(", "), span, )), } diff --git a/psl/psl-core/src/validate/validation_pipeline/validations.rs b/psl/psl-core/src/validate/validation_pipeline/validations.rs index 76e277be7394..a36bdebbb52b 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations.rs @@ -116,7 +116,6 @@ pub(super) fn validate(ctx: &mut Context<'_>) { indexes::field_length_prefix_supported(index, ctx); indexes::index_algorithm_is_supported(index, ctx); indexes::hash_index_must_not_use_sort_param(index, ctx); - indexes::fulltext_index_preview_feature_enabled(index, ctx); indexes::fulltext_index_supported(index, ctx); indexes::fulltext_columns_should_not_define_length(index, ctx); indexes::fulltext_column_sort_is_supported(index, ctx); diff --git a/psl/psl-core/src/validate/validation_pipeline/validations/indexes.rs b/psl/psl-core/src/validate/validation_pipeline/validations/indexes.rs index e9bae626f374..f9b7a7c7671e 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations/indexes.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations/indexes.rs @@ -87,23 +87,6 @@ pub(crate) fn field_length_prefix_supported(index: IndexWalker<'_>, ctx: &mut Co } } -/// `@@fulltext` attribute is not available without `fullTextIndex` preview feature. -pub(crate) fn fulltext_index_preview_feature_enabled(index: IndexWalker<'_>, ctx: &mut Context<'_>) { - if ctx.preview_features.contains(PreviewFeature::FullTextIndex) { - return; - } - - if index.is_fulltext() { - let message = "You must enable `fullTextIndex` preview feature to be able to define a @@fulltext index."; - - ctx.push_error(DatamodelError::new_attribute_validation_error( - message, - index.attribute_name(), - index.ast_attribute().span, - )); - } -} - /// `@@fulltext` should only be available if we support it in the database. pub(crate) fn fulltext_index_supported(index: IndexWalker<'_>, ctx: &mut Context<'_>) { if ctx.has_capability(ConnectorCapability::FullTextIndex) { diff --git a/psl/psl/tests/attributes/index_negative.rs b/psl/psl/tests/attributes/index_negative.rs index 33ea015b7893..032068df08bd 100644 --- a/psl/psl/tests/attributes/index_negative.rs +++ b/psl/psl/tests/attributes/index_negative.rs @@ -361,33 +361,6 @@ fn hash_index_doesnt_work_on_sqlserver() { expectation.assert_eq(&error) } -#[test] -fn fulltext_index_no_preview_feature() { - let dml = indoc! {r#" - model A { - id Int @id - a String - b String - - @@fulltext([a, b]) - } - "#}; - - let schema = with_header(dml, Provider::Mysql, &[]); - let error = parse_unwrap_err(&schema); - - let expectation = expect![[r#" - error: Error parsing attribute "@@fulltext": You must enable `fullTextIndex` preview feature to be able to define a @@fulltext index. - --> schema.prisma:16 -  |  - 15 |  - 16 |  @@fulltext([a, b]) -  |  - "#]]; - - expectation.assert_eq(&error) -} - #[test] fn hash_index_doesnt_work_on_mysql() { let dml = indoc! {r#" diff --git a/psl/psl/tests/config/generators.rs b/psl/psl/tests/config/generators.rs index 20e8f8864402..ef499223d517 100644 --- a/psl/psl/tests/config/generators.rs +++ b/psl/psl/tests/config/generators.rs @@ -258,7 +258,7 @@ fn nice_error_for_unknown_generator_preview_feature() { .unwrap_err(); let expectation = expect![[r#" - error: The preview feature "foo" is not known. Expected one of: deno, driverAdapters, fullTextIndex, fullTextSearch, metrics, multiSchema, nativeDistinct, postgresqlExtensions, tracing, views, relationJoins, prismaSchemaFolder, omitApi, strictUndefinedChecks + error: The preview feature "foo" is not known. Expected one of: deno, driverAdapters, fullTextSearch, metrics, multiSchema, nativeDistinct, postgresqlExtensions, tracing, views, relationJoins, prismaSchemaFolder, omitApi, strictUndefinedChecks --> schema.prisma:3  |   2 |  provider = "prisma-client-js" diff --git a/psl/psl/tests/validation/preview_features/full_text_search/mysql.prisma b/psl/psl/tests/validation/preview_features/full_text_search/mysql.prisma new file mode 100644 index 000000000000..3df57b035408 --- /dev/null +++ b/psl/psl/tests/validation/preview_features/full_text_search/mysql.prisma @@ -0,0 +1,29 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["fullTextSearch", "fullTextIndex"] +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +model Blog { + id Int @unique + content String + title String + + @@fulltext([content, title]) +} +// warning: Preview feature "fullTextSearch" is deprecated. The functionality can be used without specifying it as a preview feature. +// --> schema.prisma:3 +//  |  +//  2 |  provider = "prisma-client-js" +//  3 |  previewFeatures = ["fullTextSearch", "fullTextIndex"] +//  |  +// warning: Preview feature "fullTextIndex" is deprecated. The functionality can be used without specifying it as a preview feature. +// --> schema.prisma:3 +//  |  +//  2 |  provider = "prisma-client-js" +//  3 |  previewFeatures = ["fullTextSearch", "fullTextIndex"] +//  |  diff --git a/psl/psl/tests/validation/preview_features/full_text_search/postgres.prisma b/psl/psl/tests/validation/preview_features/full_text_search/postgres.prisma new file mode 100644 index 000000000000..e244e46d00f9 --- /dev/null +++ b/psl/psl/tests/validation/preview_features/full_text_search/postgres.prisma @@ -0,0 +1,17 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["fullTextSearch"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Blog { + id Int @unique + content String + title String + + @@index([content, title]) +}