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(schema-cli): support multi file schema #4874

Merged
merged 4 commits into from
May 28, 2024
Merged
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
56 changes: 43 additions & 13 deletions libs/test-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,21 +196,26 @@ async fn main() -> anyhow::Result<()> {
);
}

let schema = if let Some(file_path) = file_path {
read_datamodel_from_file(&file_path)?
} else if let Some(url) = url {
minimal_schema_from_url(&url)?
let schema = if let Some(file_path) = &file_path {
read_datamodel_from_file(file_path)?
} else if let Some(url) = &url {
minimal_schema_from_url(url)?
} else {
unreachable!()
};

let api = schema_core::schema_api(Some(schema.clone()), None)?;

let params = IntrospectParams {
schema,
schema: SchemasContainer {
files: vec![SchemaContainer {
path: file_path.unwrap_or_else(|| "schema.prisma".to_string()),
content: schema,
}],
},
force: false,
composite_type_depth: composite_type_depth.unwrap_or(0),
schemas: None,
namespaces: None,
};

let introspected = api.introspect(params).await.map_err(|err| anyhow::anyhow!("{err:?}"))?;
Expand Down Expand Up @@ -240,7 +245,12 @@ async fn main() -> anyhow::Result<()> {
let api = schema_core::schema_api(Some(schema.clone()), None)?;

api.create_database(CreateDatabaseParams {
datasource: DatasourceParam::SchemaString(SchemaContainer { schema }),
datasource: DatasourceParam::Schema(SchemasContainer {
files: vec![SchemaContainer {
path: cmd.schema_path.to_owned(),
content: schema,
}],
}),
})
.await?;
}
Expand All @@ -252,7 +262,12 @@ async fn main() -> anyhow::Result<()> {

let input = CreateMigrationInput {
migrations_directory_path: cmd.migrations_path,
prisma_schema,
schema: SchemasContainer {
files: vec![SchemaContainer {
path: cmd.schema_path,
content: prisma_schema,
}],
},
migration_name: cmd.name,
draft: true,
};
Expand Down Expand Up @@ -315,10 +330,15 @@ async fn generate_dmmf(cmd: &DmmfCommand) -> anyhow::Result<()> {
let api = schema_core::schema_api(Some(skeleton.clone()), None)?;

let params = IntrospectParams {
schema: skeleton,
schema: SchemasContainer {
files: vec![SchemaContainer {
path: "schema.prisma".to_string(),
content: skeleton,
}],
},
force: false,
composite_type_depth: -1,
schemas: None,
namespaces: None,
};

let introspected = api.introspect(params).await.map_err(|err| anyhow::anyhow!("{err:?}"))?;
Expand Down Expand Up @@ -355,7 +375,12 @@ async fn schema_push(cmd: &SchemaPush) -> anyhow::Result<()> {

let response = api
.schema_push(SchemaPushInput {
schema,
schema: SchemasContainer {
files: vec![SchemaContainer {
path: cmd.schema_path.clone(),
content: schema,
}],
},
force: cmd.force,
})
.await?;
Expand Down Expand Up @@ -414,8 +439,13 @@ async fn migrate_diff(cmd: &MigrateDiff) -> anyhow::Result<()> {

let api = schema_core::schema_api(None, Some(Arc::new(DiffHost)))?;
let to = if let Some(to_schema_datamodel) = &cmd.to_schema_datamodel {
DiffTarget::SchemaDatamodel(SchemaContainer {
schema: to_schema_datamodel.clone(),
let to_schema_datamodel_str = std::fs::read_to_string(to_schema_datamodel)?;

DiffTarget::SchemaDatamodel(SchemasContainer {
files: vec![SchemaContainer {
path: to_schema_datamodel.to_owned(),
content: to_schema_datamodel_str,
}],
})
} else {
todo!("can't handle {:?} yet", cmd)
Expand Down
2 changes: 1 addition & 1 deletion prisma-fmt/src/code_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub(crate) fn available_actions(
) -> Vec<CodeActionOrCommand> {
let mut actions = Vec::new();

let validated_schema = psl::validate_multi_file(schema_files);
let validated_schema = psl::validate_multi_file(&schema_files);

let config = &validated_schema.configuration;

Expand Down
2 changes: 1 addition & 1 deletion prisma-fmt/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct MiniError {
pub(crate) fn run(schema: SchemaFileInput) -> String {
let schema = match schema {
SchemaFileInput::Single(file) => psl::validate(file.into()),
SchemaFileInput::Multiple(files) => psl::validate_multi_file(files),
SchemaFileInput::Multiple(files) => psl::validate_multi_file(&files),
};
let diagnostics = &schema.diagnostics;

Expand Down
3 changes: 2 additions & 1 deletion prisma-fmt/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ pub(crate) fn validate(params: &str) -> Result<(), String> {
}

pub fn run(input_schema: SchemaFileInput, no_color: bool) -> Result<ValidatedSchema, String> {
let validate_schema = psl::validate_multi_file(input_schema.into());
let sources: Vec<(String, psl::SourceFile)> = input_schema.into();
let validate_schema = psl::validate_multi_file(&sources);
let diagnostics = &validate_schema.diagnostics;

if !diagnostics.has_errors() {
Expand Down
11 changes: 5 additions & 6 deletions prisma-fmt/tests/code_actions/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ const TARGET_SCHEMA_FILE: &str = "_target.prisma";
static UPDATE_EXPECT: Lazy<bool> = Lazy::new(|| std::env::var("UPDATE_EXPECT").is_ok());

fn parse_schema_diagnostics(files: &[(String, String)], initiating_file_name: &str) -> Option<Vec<Diagnostic>> {
let schema = psl::validate_multi_file(
files
.iter()
.map(|(name, content)| (name.to_owned(), SourceFile::from(content)))
.collect(),
);
let sources: Vec<_> = files
.iter()
.map(|(name, content)| (name.to_owned(), SourceFile::from(content)))
.collect();
let schema = psl::validate_multi_file(&sources);

let file_id = schema.db.file_id(initiating_file_name).unwrap();
let source = schema.db.source(file_id);
Expand Down
5 changes: 5 additions & 0 deletions psl/parser-database/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ impl ParserDatabase {
self.asts.iter().map(|ast| ast.2.as_str())
}

/// Iterate all source file contents and their file paths.
pub fn iter_file_sources(&self) -> impl Iterator<Item = (&str, &SourceFile)> {
self.asts.iter().map(|ast| (ast.1.as_str(), ast.2))
}

/// The name of the file.
pub fn file_name(&self, file_id: FileId) -> &str {
self.asts[file_id].0.as_str()
Expand Down
4 changes: 2 additions & 2 deletions psl/psl-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ pub fn validate(file: SourceFile, connectors: ConnectorRegistry<'_>) -> Validate

/// The most general API for dealing with Prisma schemas. It accumulates what analysis and
/// validation information it can, and returns it along with any error and warning diagnostics.
pub fn validate_multi_file(files: Vec<(String, SourceFile)>, connectors: ConnectorRegistry<'_>) -> ValidatedSchema {
pub fn validate_multi_file(files: &[(String, SourceFile)], connectors: ConnectorRegistry<'_>) -> ValidatedSchema {
assert!(
!files.is_empty(),
"psl::validate_multi_file() must be called with at least one file"
);
let mut diagnostics = Diagnostics::new();
let db = ParserDatabase::new(&files, &mut diagnostics);
let db = ParserDatabase::new(files, &mut diagnostics);

// TODO: the bulk of configuration block analysis should be part of ParserDatabase::new().
let mut configuration = Configuration::default();
Expand Down
14 changes: 13 additions & 1 deletion psl/psl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ pub fn parse_schema(file: impl Into<SourceFile>) -> Result<ValidatedSchema, Stri
Ok(schema)
}

/// Parse and analyze a Prisma schema.
pub fn parse_schema_multi(files: &[(String, SourceFile)]) -> Result<ValidatedSchema, String> {
let mut schema = validate_multi_file(files);

schema
.diagnostics
.to_result()
.map_err(|err| schema.db.render_diagnostics(&err))?;

Ok(schema)
}

/// The most general API for dealing with Prisma schemas. It accumulates what analysis and
/// validation information it can, and returns it along with any error and warning diagnostics.
pub fn validate(file: SourceFile) -> ValidatedSchema {
Expand All @@ -71,6 +83,6 @@ pub fn parse_without_validation(file: SourceFile, connector_registry: ConnectorR
}
/// The most general API for dealing with Prisma schemas. It accumulates what analysis and
/// validation information it can, and returns it along with any error and warning diagnostics.
pub fn validate_multi_file(files: Vec<(String, SourceFile)>) -> ValidatedSchema {
pub fn validate_multi_file(files: &[(String, SourceFile)]) -> ValidatedSchema {
psl_core::validate_multi_file(files, builtin_connectors::BUILTIN_CONNECTORS)
}
4 changes: 2 additions & 2 deletions psl/psl/tests/multi_file/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use crate::common::expect;

fn expect_errors(schemas: &[[&'static str; 2]], expectation: expect_test::Expect) {
let out = psl::validate_multi_file(
schemas
&schemas
.iter()
.map(|[file_name, contents]| ((*file_name).into(), (*contents).into()))
.collect(),
.collect::<Vec<_>>(),
);

let actual = out.render_own_diagnostics();
Expand Down
28 changes: 27 additions & 1 deletion psl/schema-ast/src/source_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::sync::Arc;
use serde::{Deserialize, Deserializer};

/// A Prisma schema document.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SourceFile {
contents: Contents,
}
Expand Down Expand Up @@ -82,3 +82,29 @@ enum Contents {
Static(&'static str),
Allocated(Arc<str>),
}

impl std::hash::Hash for Contents {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Contents::Static(s) => (*s).hash(state),
Contents::Allocated(s) => {
let s: &str = s;

s.hash(state);
}
}
}
}

impl Eq for Contents {}

impl PartialEq for Contents {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Contents::Static(l), Contents::Static(r)) => l == r,
(Contents::Allocated(l), Contents::Allocated(r)) => l == r,
(Contents::Static(l), Contents::Allocated(r)) => *l == &**r,
(Contents::Allocated(l), Contents::Static(r)) => &**l == *r,
}
}
}
6 changes: 5 additions & 1 deletion query-engine/connector-test-kit-rs/qe-setup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ pub(crate) async fn diff(schema: &str, url: String, connector: &mut dyn SchemaCo
.database_schema_from_diff_target(DiffTarget::Empty, None, None)
.await?;
let to = connector
.database_schema_from_diff_target(DiffTarget::Datamodel(schema.into()), None, None)
.database_schema_from_diff_target(
DiffTarget::Datamodel(vec![("schema.prisma".to_string(), schema.into())]),
None,
None,
)
.await?;
let migration = connector.diff(from, to);
connector.render_script(&migration, &Default::default())
Expand Down
35 changes: 20 additions & 15 deletions schema-engine/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(version = env!("GIT_HASH"))]
struct SchemaEngineCli {
/// Path to the datamodel
/// List of paths to the Prisma schema files.
#[structopt(short = "d", long, name = "FILE")]
datamodel: Option<String>,
datamodels: Option<Vec<String>>,
#[structopt(subcommand)]
cli_subcommand: Option<SubCommand>,
}
Expand All @@ -36,7 +36,7 @@ async fn main() {
let input = SchemaEngineCli::from_args();

match input.cli_subcommand {
None => start_engine(input.datamodel.as_deref()).await,
None => start_engine(input.datamodels).await,
Some(SubCommand::Cli(cli_command)) => {
tracing::info!(git_hash = env!("GIT_HASH"), "Starting schema engine CLI");
cli_command.run().await;
Expand Down Expand Up @@ -91,30 +91,35 @@ impl ConnectorHost for JsonRpcHost {
}
}

async fn start_engine(datamodel_location: Option<&str>) {
async fn start_engine(datamodel_locations: Option<Vec<String>>) {
use std::io::Read as _;

tracing::info!(git_hash = env!("GIT_HASH"), "Starting schema engine RPC server",);

let datamodel = datamodel_location.map(|location| {
let mut file = match std::fs::File::open(location) {
Ok(file) => file,
Err(e) => panic!("Error opening datamodel file in `{location}`: {e}"),
};
let datamodel_locations = datamodel_locations.map(|datamodel_locations| {
datamodel_locations
.into_iter()
.map(|location| {
let mut file = match std::fs::File::open(&location) {
Ok(file) => file,
Err(e) => panic!("Error opening datamodel file in `{location}`: {e}"),
};

let mut datamodel = String::new();
let mut datamodel = String::new();

if let Err(e) = file.read_to_string(&mut datamodel) {
panic!("Error reading datamodel file `{location}`: {e}");
};
if let Err(e) = file.read_to_string(&mut datamodel) {
panic!("Error reading datamodel file `{location}`: {e}");
};

datamodel
(location, datamodel)
})
.collect::<Vec<_>>()
});

let (client, adapter) = json_rpc_stdio::new_client();
let host = JsonRpcHost { client };

let api = rpc_api(datamodel, Arc::new(host));
let api = rpc_api(datamodel_locations, Arc::new(host));
// Block the thread and handle IO in async until EOF.
json_rpc_stdio::run_with_client(&api, adapter).await.unwrap();
}
Loading
Loading