Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(psl): GA fullTextIndex and fullTextSearch for provider = "mysql" #5031

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion psl/psl-core/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
43 changes: 34 additions & 9 deletions psl/psl-core/src/common/preview_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -117,6 +116,7 @@ pub const ALL_PREVIEW_FEATURES: FeatureMap = FeatureMap {
| FieldReference
| FilteredRelationCount
| FilterJson
| FullTextIndex
| GroupBy
| ImprovedQueryRaw
| InteractiveTransactions
Expand Down Expand Up @@ -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("<default>");

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)
}
}

Expand Down
14 changes: 12 additions & 2 deletions psl/psl-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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())
}
37 changes: 27 additions & 10 deletions psl/psl-core/src/validate/generator_loader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
ast::WithSpan,
common::{FeatureMap, PreviewFeature, ALL_PREVIEW_FEATURES},
common::{FeatureMapWithProvider, PreviewFeature},
configuration::{Generator, GeneratorConfigValue, StringFromEnvVar},
diagnostics::*,
};
Expand All @@ -22,19 +22,27 @@ 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<Generator> {
pub(crate) fn load_generators_from_ast(
ast_schema: &ast::SchemaAst,
diagnostics: &mut Diagnostics,
feature_map_with_provider: &FeatureMapWithProvider<'_>,
) -> Vec<Generator> {
let mut generators: Vec<Generator> = 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);
}
}

generators
}

fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagnostics) -> Option<Generator> {
fn lift_generator(
ast_generator: &ast::GeneratorConfig,
diagnostics: &mut Diagnostics,
feature_map_with_provider: &FeatureMapWithProvider<'_>,
) -> Option<Generator> {
let generator_name = ast_generator.name.name.as_str();
let args: HashMap<_, &Expression> = ast_generator
.properties
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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<PreviewFeature> {
Expand All @@ -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,
))
}
Expand All @@ -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,
)),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
27 changes: 0 additions & 27 deletions psl/psl/tests/attributes/index_negative.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down
2 changes: 1 addition & 1 deletion psl/psl/tests/config/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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"]
//  | 
Original file line number Diff line number Diff line change
@@ -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])
}
Loading