Skip to content

Commit

Permalink
feat(schema-cli): support multi file schema (#4874)
Browse files Browse the repository at this point in the history
* feat(schema-cli): support multi file schema

remove dead code

fix tests

* fix review

* tests + cleanup

* fix tests
  • Loading branch information
Weakky authored May 28, 2024
1 parent 0ae1cc9 commit bca2d36
Show file tree
Hide file tree
Showing 61 changed files with 1,414 additions and 415 deletions.
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

0 comments on commit bca2d36

Please sign in to comment.