diff --git a/.gitignore b/.gitignore index 0c2f9ea43181..7cb8421c5544 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,6 @@ graph.dot prisma-schema-wasm/nodejs -# Ignore pnpm-lock.yaml -query-engine/driver-adapters/pnpm-lock.yaml package-lock.json # Useful for local wasm32-* development diff --git a/Cargo.lock b/Cargo.lock index b915dbf08a69..b31f4e1bd973 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,31 +938,45 @@ dependencies = [ [[package]] name = "cuid" -version = "1.3.2" -source = "git+https://github.com/prisma/cuid-rust?branch=wasm32-support#ccfd958c224c79758c2527a0bca9efcd71790a19" +version = "1.3.3" +source = "git+https://github.com/prisma/cuid-rust?branch=v1.3.3-wasm32-unknown-unknown#dc68c4f47a3dbcd511f605135ac7c948775a3ab9" dependencies = [ - "base36", - "cuid-util", + "cuid1", "cuid2", "getrandom 0.2.11", - "js-sys", - "num", - "once_cell", - "rand 0.8.5", - "sha3", ] [[package]] name = "cuid-util" +version = "0.1.1" +source = "git+https://github.com/prisma/cuid-rust?branch=v1.3.3-wasm32-unknown-unknown#dc68c4f47a3dbcd511f605135ac7c948775a3ab9" +dependencies = [ + "js-sys", + "web-time", +] + +[[package]] +name = "cuid1" version = "0.1.0" -source = "git+https://github.com/prisma/cuid-rust?branch=wasm32-support#ccfd958c224c79758c2527a0bca9efcd71790a19" +source = "git+https://github.com/prisma/cuid-rust?branch=v1.3.3-wasm32-unknown-unknown#dc68c4f47a3dbcd511f605135ac7c948775a3ab9" +dependencies = [ + "base36", + "cuid-util", + "getrandom 0.2.11", + "hostname 0.4.0", + "num", + "once_cell", + "rand 0.8.5", + "uuid", +] [[package]] name = "cuid2" -version = "0.1.2" -source = "git+https://github.com/prisma/cuid-rust?branch=wasm32-support#ccfd958c224c79758c2527a0bca9efcd71790a19" +version = "0.1.3" +source = "git+https://github.com/prisma/cuid-rust?branch=v1.3.3-wasm32-unknown-unknown#dc68c4f47a3dbcd511f605135ac7c948775a3ab9" dependencies = [ "cuid-util", + "getrandom 0.2.11", "num", "rand 0.8.5", "sha3", @@ -1828,6 +1842,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows 0.52.0", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -1942,7 +1967,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] @@ -2102,9 +2127,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -2560,7 +2585,6 @@ dependencies = [ "bigdecimal", "bson", "chrono", - "cuid", "derive_more", "futures", "indexmap 2.2.2", @@ -3139,6 +3163,7 @@ dependencies = [ "either", "enumflags2", "indexmap 2.2.2", + "itertools 0.12.0", "rustc-hash", "schema-ast", ] @@ -3963,6 +3988,7 @@ dependencies = [ "base64 0.13.1", "chrono", "colored", + "cuid", "enumflags2", "futures", "indoc 2.0.3", @@ -4411,7 +4437,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ - "hostname", + "hostname 0.3.1", "quick-error", ] @@ -6427,6 +6453,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki" version = "0.21.4" @@ -6524,6 +6560,25 @@ dependencies = [ "windows-targets 0.48.1", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index df9f44eee292..397b1c70e471 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,9 @@ chrono = { version = "0.4.38", features = ["serde"] } derive_more = "0.99.17" user-facing-errors = { path = "./libs/user-facing-errors" } uuid = { version = "1", features = ["serde", "v4", "v7", "js"] } +cuid = { git = "https://github.com/prisma/cuid-rust", branch = "v1.3.3-wasm32-unknown-unknown" } +getrandom = { version = "0.2" } + indoc = "2.0.1" indexmap = { version = "2.2.2", features = ["serde"] } itertools = "0.12" @@ -64,7 +67,6 @@ napi = { version = "2.16.13", default-features = false, features = [ "serde-json", ] } napi-derive = "2.16.12" -js-sys = { version = "0.3" } pin-project = "1" rand = { version = "0.8" } regex = { version = "1", features = ["std"] } @@ -73,6 +75,11 @@ serde-wasm-bindgen = { version = "0.5" } tracing = { version = "0.1" } tracing-futures = "0.2" tsify = { version = "0.4.5" } + +# version for `wasm-bindgen@0.2.93`, see: +# https://github.com/rustwasm/wasm-bindgen/pull/4072/ +js-sys = { version = "0.3.70" } + wasm-bindgen = { version = "0.2.93" } wasm-bindgen-futures = { version = "0.4" } wasm-rs-dbg = { version = "0.1.2", default-features = false, features = ["console-error"] } diff --git a/libs/telemetry/Cargo.toml b/libs/telemetry/Cargo.toml index 4b9f9b79dacc..51eff7623aae 100644 --- a/libs/telemetry/Cargo.toml +++ b/libs/telemetry/Cargo.toml @@ -25,7 +25,7 @@ tracing-futures = "0.2" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-opentelemetry = "0.17.4" uuid.workspace = true -cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } +cuid.workspace = true crosstarget-utils = { path = "../crosstarget-utils" } lru = "0.7.7" enumflags2.workspace = true diff --git a/psl/parser-database/Cargo.toml b/psl/parser-database/Cargo.toml index 2ae58c8b3850..fa0a6d5afb13 100644 --- a/psl/parser-database/Cargo.toml +++ b/psl/parser-database/Cargo.toml @@ -8,5 +8,6 @@ diagnostics = { path = "../diagnostics" } schema-ast = { path = "../schema-ast" } indexmap.workspace = true enumflags2.workspace = true +itertools.workspace = true either = "1.6.1" rustc-hash = "1.1.0" diff --git a/psl/parser-database/src/attributes/default.rs b/psl/parser-database/src/attributes/default.rs index d1f6b887fa22..157df005e612 100644 --- a/psl/parser-database/src/attributes/default.rs +++ b/psl/parser-database/src/attributes/default.rs @@ -2,6 +2,7 @@ use crate::{ ast::{self, WithName}, coerce, context::Context, + generators::{CUID_SUPPORTED_VERSIONS, UUID_SUPPORTED_VERSIONS}, types::{DefaultAttribute, ScalarFieldType, ScalarType}, DatamodelError, ScalarFieldId, StringId, }; @@ -197,10 +198,10 @@ fn validate_model_builtin_scalar_type_default( validate_empty_function_args(funcname, &funcargs.arguments, accept, ctx) } (ScalarType::String, ast::Expression::Function(funcname, funcargs, _)) if funcname == FN_CUID => { - validate_empty_function_args(funcname, &funcargs.arguments, accept, ctx) + validate_uid_int_args(funcname, &funcargs.arguments, &CUID_SUPPORTED_VERSIONS, accept, ctx) } (ScalarType::String, ast::Expression::Function(funcname, funcargs, _)) if funcname == FN_UUID => { - validate_uuid_args(&funcargs.arguments, accept, ctx) + validate_uid_int_args(funcname, &funcargs.arguments, &UUID_SUPPORTED_VERSIONS, accept, ctx) } (ScalarType::String, ast::Expression::Function(funcname, funcargs, _)) if funcname == FN_NANOID => { validate_nanoid_args(&funcargs.arguments, accept, ctx) @@ -244,10 +245,10 @@ fn validate_composite_builtin_scalar_type_default( match (scalar_type, value) { // Functions (ScalarType::String, ast::Expression::Function(funcname, funcargs, _)) if funcname == FN_CUID => { - validate_empty_function_args(funcname, &funcargs.arguments, accept, ctx) + validate_uid_int_args(funcname, &funcargs.arguments, &CUID_SUPPORTED_VERSIONS, accept, ctx) } (ScalarType::String, ast::Expression::Function(funcname, funcargs, _)) if funcname == FN_UUID => { - validate_uuid_args(&funcargs.arguments, accept, ctx) + validate_uid_int_args(funcname, &funcargs.arguments, &UUID_SUPPORTED_VERSIONS, accept, ctx) } (ScalarType::DateTime, ast::Expression::Function(funcname, funcargs, _)) if funcname == FN_NOW => { validate_empty_function_args(FN_NOW, &funcargs.arguments, accept, ctx) @@ -381,18 +382,40 @@ fn validate_dbgenerated_args(args: &[ast::Argument], accept: AcceptFn<'_>, ctx: } } -fn validate_uuid_args(args: &[ast::Argument], accept: AcceptFn<'_>, ctx: &mut Context<'_>) { - let mut bail = || ctx.push_attribute_validation_error("`uuid()` takes a single Int argument."); +fn format_valid_values(valid_values: &[u8; N]) -> String { + match valid_values.len() { + 0 => String::new(), + 1 => valid_values[0].to_string(), + 2 => format!("{} or {}", valid_values[0], valid_values[1]), + _ => { + use itertools::Itertools as _; + + let (last, rest) = valid_values.split_last().unwrap(); + let rest_str = rest.iter().map(|v| v.to_string()).join(", "); + format!("{}, or {}", rest_str, last) + } + } +} + +fn validate_uid_int_args( + fn_name: &str, + args: &[ast::Argument], + valid_values: &[u8; N], + accept: AcceptFn<'_>, + ctx: &mut Context<'_>, +) { + let mut bail = || ctx.push_attribute_validation_error(&format!("`{fn_name}()` takes a single Int argument.")); if args.len() > 1 { bail() } match args.first().map(|arg| &arg.value) { - Some(ast::Expression::NumericValue(val, _)) if ![4u8, 7u8].contains(&val.parse::().unwrap()) => { - ctx.push_attribute_validation_error( - "`uuid()` takes either no argument, or a single integer argument which is either 4 or 7.", - ); + Some(ast::Expression::NumericValue(val, _)) if !valid_values.contains(&val.parse::().unwrap()) => { + let valid_values_str = format_valid_values(valid_values); + ctx.push_attribute_validation_error(&format!( + "`{fn_name}()` takes either no argument, or a single integer argument which is either {valid_values_str}.", + )); } None | Some(ast::Expression::NumericValue(_, _)) => accept(ctx), _ => bail(), diff --git a/psl/parser-database/src/generators.rs b/psl/parser-database/src/generators.rs new file mode 100644 index 000000000000..1a2ede510ec1 --- /dev/null +++ b/psl/parser-database/src/generators.rs @@ -0,0 +1,16 @@ +//! Convenient access to a ID generator constants, used by Prisma in psl, Query Engine and Schema Engine. +//! Every change to the `DEFAULT_*_VERSION` constants in this module is a breaking change. + +/// Version of the `uuid()` ID generator supported by Prisma. +pub const UUID_SUPPORTED_VERSIONS: [u8; 2] = [4, 7]; + +/// Version of the `cuid()` ID generator supported by Prisma. +pub const CUID_SUPPORTED_VERSIONS: [u8; 2] = [1, 2]; + +/// Default version of the `uuid()` ID generator. +pub const DEFAULT_UUID_VERSION: u8 = 4; + +/// Default version of the `cuid()` ID generator. +// Note: if you change this, you'll likely need to adapt existing tests that rely on `cuid()` sequences being already sorted +// (e.g., `cuid(1)`, the current default, generates monotonically increasing sequences, `cuid(2)` doesn't). +pub const DEFAULT_CUID_VERSION: u8 = 1; diff --git a/psl/parser-database/src/lib.rs b/psl/parser-database/src/lib.rs index 5ada8cebb961..087abc42c970 100644 --- a/psl/parser-database/src/lib.rs +++ b/psl/parser-database/src/lib.rs @@ -32,6 +32,7 @@ mod attributes; mod coerce_expression; mod context; mod files; +pub mod generators; mod ids; mod interner; mod names; diff --git a/psl/psl-core/src/lib.rs b/psl/psl-core/src/lib.rs index 304ef3cc5cfe..e4b36dd15e92 100644 --- a/psl/psl-core/src/lib.rs +++ b/psl/psl-core/src/lib.rs @@ -25,7 +25,7 @@ pub use crate::{ reformat::{reformat, reformat_multiple, reformat_validated_schema_into_single}, }; pub use diagnostics; -pub use parser_database::{self, is_reserved_type_name}; +pub use parser_database::{self, generators, is_reserved_type_name}; pub use schema_ast; pub use set_config_dir::set_config_dir; diff --git a/psl/psl/src/lib.rs b/psl/psl/src/lib.rs index a0085d2b790d..b58b84d95546 100644 --- a/psl/psl/src/lib.rs +++ b/psl/psl/src/lib.rs @@ -7,6 +7,7 @@ pub use psl_core::{ builtin_connectors::{can_have_capability, can_support_relation_load_strategy, has_capability}, datamodel_connector, diagnostics::{self, Diagnostics}, + generators, is_reserved_type_name, mcf::config_to_mcf_json_value as get_config, mcf::{generators_to_json, render_sources_to_json}, // for tests diff --git a/psl/psl/tests/attributes/id_positive.rs b/psl/psl/tests/attributes/id_positive.rs index e81c1305c274..e6e7ec010295 100644 --- a/psl/psl/tests/attributes/id_positive.rs +++ b/psl/psl/tests/attributes/id_positive.rs @@ -50,6 +50,45 @@ fn should_allow_string_ids_with_cuid() { model.assert_id_on_fields(&["id"]); } +#[test] +fn should_allow_string_ids_with_cuid_version_specified() { + let dml = indoc! {r#" + model ModelA { + id String @id @default(cuid(1)) + } + + model ModelB { + id String @id @default(cuid(2)) + } + "#}; + + let schema = psl::parse_schema(dml).unwrap(); + + { + let model = schema.assert_has_model("ModelA"); + + model + .assert_has_scalar_field("id") + .assert_scalar_type(ScalarType::String) + .assert_default_value() + .assert_cuid_version(1); + + model.assert_id_on_fields(&["id"]); + } + + { + let model = schema.assert_has_model("ModelB"); + + model + .assert_has_scalar_field("id") + .assert_scalar_type(ScalarType::String) + .assert_default_value() + .assert_cuid_version(2); + + model.assert_id_on_fields(&["id"]); + } +} + #[test] fn should_allow_string_ids_with_uuid() { let dml = indoc! {r#" @@ -91,7 +130,7 @@ fn should_allow_string_ids_with_uuid_version_specified() { .assert_has_scalar_field("id") .assert_scalar_type(ScalarType::String) .assert_default_value() - .assert_uuid(); + .assert_uuid_version(4); model.assert_id_on_fields(&["id"]); } @@ -103,7 +142,7 @@ fn should_allow_string_ids_with_uuid_version_specified() { .assert_has_scalar_field("id") .assert_scalar_type(ScalarType::String) .assert_default_value() - .assert_uuid(); + .assert_uuid_version(7); model.assert_id_on_fields(&["id"]); } diff --git a/psl/psl/tests/common/asserts.rs b/psl/psl/tests/common/asserts.rs index e75df7c2cd7b..97eb35bc1cce 100644 --- a/psl/psl/tests/common/asserts.rs +++ b/psl/psl/tests/common/asserts.rs @@ -83,7 +83,9 @@ pub(crate) trait DefaultValueAssert { fn assert_bytes(&self, val: &[u8]) -> &Self; fn assert_now(&self) -> &Self; fn assert_cuid(&self) -> &Self; + fn assert_cuid_version(&self, version: u8) -> &Self; fn assert_uuid(&self) -> &Self; + fn assert_uuid_version(&self, version: u8) -> &Self; fn assert_dbgenerated(&self, val: &str) -> &Self; fn assert_mapped_name(&self, val: &str) -> &Self; } @@ -433,12 +435,24 @@ impl<'a> DefaultValueAssert for walkers::DefaultValueWalker<'a> { self } + #[track_caller] + fn assert_cuid_version(&self, version: u8) -> &Self { + self.value().assert_cuid_version(version); + self + } + #[track_caller] fn assert_uuid(&self) -> &Self { self.value().assert_uuid(); self } + #[track_caller] + fn assert_uuid_version(&self, version: u8) -> &Self { + self.value().assert_uuid_version(version); + self + } + #[track_caller] fn assert_dbgenerated(&self, val: &str) -> &Self { self.value().assert_dbgenerated(val); @@ -622,9 +636,24 @@ impl DefaultValueAssert for ast::Expression { #[track_caller] fn assert_cuid(&self) -> &Self { - assert!( - matches!(self, ast::Expression::Function(name, args, _) if name == "cuid" && args.arguments.is_empty()) - ); + assert!(matches!(self, ast::Expression::Function(name, _, _) if name == "cuid")); + + self + } + + #[track_caller] + fn assert_cuid_version(&self, version: u8) -> &Self { + self.assert_cuid(); + + if let ast::Expression::Function(_, args, _) = self { + if let ast::Expression::NumericValue(actual, _) = &args.arguments[0].value { + assert_eq!(actual, &format!("{version}")); + } else { + panic!("Expected a numeric value for the `cuid()` version."); + } + } else { + panic!("Expected `cuid()` to be a function, got {}", &self); + } self } @@ -636,6 +665,23 @@ impl DefaultValueAssert for ast::Expression { self } + #[track_caller] + fn assert_uuid_version(&self, version: u8) -> &Self { + self.assert_uuid(); + + if let ast::Expression::Function(_, args, _) = self { + if let ast::Expression::NumericValue(actual, _) = &args.arguments[0].value { + assert_eq!(actual, &version.to_string()); + } else { + panic!("Expected a numeric value for the `uuid()` version."); + } + } else { + panic!("Expected `cuid()` to be a function, got {}", &self); + } + + self + } + #[track_caller] fn assert_dbgenerated(&self, val: &str) -> &Self { match self { diff --git a/psl/psl/tests/validation/attributes/cuid/reject_random_int.prisma b/psl/psl/tests/validation/attributes/cuid/reject_random_int.prisma new file mode 100644 index 000000000000..52d5a0adee7e --- /dev/null +++ b/psl/psl/tests/validation/attributes/cuid/reject_random_int.prisma @@ -0,0 +1,14 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(cuid(42)) +} +// error: Error parsing attribute "@default": `cuid()` takes either no argument, or a single integer argument which is either 1 or 2. +// --> schema.prisma:7 +//  |  +//  6 | model Category { +//  7 |  id String @id @default(cuid(42)) +//  |  diff --git a/psl/psl/tests/validation/attributes/cuid/reject_string_argument.prisma b/psl/psl/tests/validation/attributes/cuid/reject_string_argument.prisma new file mode 100644 index 000000000000..1ea3fa9acd71 --- /dev/null +++ b/psl/psl/tests/validation/attributes/cuid/reject_string_argument.prisma @@ -0,0 +1,14 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(cuid("asdf")) +} +// error: Error parsing attribute "@default": `cuid()` takes a single Int argument. +// --> schema.prisma:7 +//  |  +//  6 | model Category { +//  7 |  id String @id @default(cuid("asdf")) +//  |  diff --git a/psl/psl/tests/validation/attributes/cuid/require_string_column.prisma b/psl/psl/tests/validation/attributes/cuid/require_string_column.prisma new file mode 100644 index 000000000000..b5a054bf9598 --- /dev/null +++ b/psl/psl/tests/validation/attributes/cuid/require_string_column.prisma @@ -0,0 +1,14 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id Int @id @default(cuid()) +} +// error: Error parsing attribute "@default": The function `cuid()` cannot be used on fields of type `Int`. +// --> schema.prisma:7 +//  |  +//  6 | model Category { +//  7 |  id Int @id @default(cuid()) +//  |  diff --git a/psl/psl/tests/validation/attributes/cuid/string_column_with_int_1_arg_valid.prisma b/psl/psl/tests/validation/attributes/cuid/string_column_with_int_1_arg_valid.prisma new file mode 100644 index 000000000000..26d2647bd9de --- /dev/null +++ b/psl/psl/tests/validation/attributes/cuid/string_column_with_int_1_arg_valid.prisma @@ -0,0 +1,8 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(cuid(1)) +} diff --git a/psl/psl/tests/validation/attributes/cuid/string_column_with_int_2_arg_valid.prisma b/psl/psl/tests/validation/attributes/cuid/string_column_with_int_2_arg_valid.prisma new file mode 100644 index 000000000000..eaa44fbbb3e0 --- /dev/null +++ b/psl/psl/tests/validation/attributes/cuid/string_column_with_int_2_arg_valid.prisma @@ -0,0 +1,8 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(cuid(2)) +} diff --git a/psl/psl/tests/validation/attributes/cuid/string_column_with_no_arg_valid.prisma b/psl/psl/tests/validation/attributes/cuid/string_column_with_no_arg_valid.prisma new file mode 100644 index 000000000000..e4e444e5a723 --- /dev/null +++ b/psl/psl/tests/validation/attributes/cuid/string_column_with_no_arg_valid.prisma @@ -0,0 +1,8 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(cuid()) +} diff --git a/psl/psl/tests/validation/attributes/uuid/reject_random_int.prisma b/psl/psl/tests/validation/attributes/uuid/reject_random_int.prisma new file mode 100644 index 000000000000..66bb07f19136 --- /dev/null +++ b/psl/psl/tests/validation/attributes/uuid/reject_random_int.prisma @@ -0,0 +1,14 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(uuid(42)) +} +// error: Error parsing attribute "@default": `uuid()` takes either no argument, or a single integer argument which is either 4 or 7. +// --> schema.prisma:7 +//  |  +//  6 | model Category { +//  7 |  id String @id @default(uuid(42)) +//  |  diff --git a/psl/psl/tests/validation/attributes/uuid/reject_string_argument.prisma b/psl/psl/tests/validation/attributes/uuid/reject_string_argument.prisma new file mode 100644 index 000000000000..ad0a3d2c88d8 --- /dev/null +++ b/psl/psl/tests/validation/attributes/uuid/reject_string_argument.prisma @@ -0,0 +1,14 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(uuid("asdf")) +} +// error: Error parsing attribute "@default": `uuid()` takes a single Int argument. +// --> schema.prisma:7 +//  |  +//  6 | model Category { +//  7 |  id String @id @default(uuid("asdf")) +//  |  diff --git a/psl/psl/tests/validation/attributes/uuid/require_string_column.prisma b/psl/psl/tests/validation/attributes/uuid/require_string_column.prisma new file mode 100644 index 000000000000..ba2ec51e91f2 --- /dev/null +++ b/psl/psl/tests/validation/attributes/uuid/require_string_column.prisma @@ -0,0 +1,14 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id Int @id @default(uuid()) +} +// error: Error parsing attribute "@default": The function `uuid()` cannot be used on fields of type `Int`. +// --> schema.prisma:7 +//  |  +//  6 | model Category { +//  7 |  id Int @id @default(uuid()) +//  |  diff --git a/psl/psl/tests/validation/attributes/uuid/string_column_with_int_4_arg_valid.prisma b/psl/psl/tests/validation/attributes/uuid/string_column_with_int_4_arg_valid.prisma new file mode 100644 index 000000000000..bb8cfce479fd --- /dev/null +++ b/psl/psl/tests/validation/attributes/uuid/string_column_with_int_4_arg_valid.prisma @@ -0,0 +1,8 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(uuid(4)) +} diff --git a/psl/psl/tests/validation/attributes/uuid/string_column_with_int_7_arg_valid.prisma b/psl/psl/tests/validation/attributes/uuid/string_column_with_int_7_arg_valid.prisma new file mode 100644 index 000000000000..a8cb057ab2a3 --- /dev/null +++ b/psl/psl/tests/validation/attributes/uuid/string_column_with_int_7_arg_valid.prisma @@ -0,0 +1,8 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(uuid(7)) +} diff --git a/psl/psl/tests/validation/attributes/uuid/string_column_with_no_arg_valid.prisma b/psl/psl/tests/validation/attributes/uuid/string_column_with_no_arg_valid.prisma new file mode 100644 index 000000000000..669beadaa0eb --- /dev/null +++ b/psl/psl/tests/validation/attributes/uuid/string_column_with_no_arg_valid.prisma @@ -0,0 +1,8 @@ +datasource db { + provider = "mysql" + url = "does_not_matter" +} + +model Category { + id String @id @default(uuid()) +} diff --git a/quaint/Cargo.toml b/quaint/Cargo.toml index 482f6f0a7626..4e13c764b22c 100644 --- a/quaint/Cargo.toml +++ b/quaint/Cargo.toml @@ -98,6 +98,7 @@ sqlformat = { version = "0.2.3", optional = true } uuid.workspace = true crosstarget-utils = { path = "../libs/crosstarget-utils" } concat-idents = "1.1.5" +getrandom = { workspace = true, optional = true } [dev-dependencies] once_cell = "1.3" @@ -110,9 +111,8 @@ quaint-test-setup = { path = "quaint-test-setup" } tokio = { version = "1", features = ["macros", "time"] } expect-test = "1" -[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] -version = "0.2" -features = ["js"] +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true, features = ["js"] } [dependencies.ws_stream_tungstenite] version = "0.14.0" diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml b/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml index 60cfbca7ca18..89d9e18cc997 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml +++ b/query-engine/connector-test-kit-rs/query-engine-tests/Cargo.toml @@ -17,6 +17,7 @@ chrono.workspace = true psl.workspace = true base64 = "0.13" uuid.workspace = true +cuid.workspace = true tokio.workspace = true user-facing-errors.workspace = true prisma-value = { path = "../../../libs/prisma-value" } diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/ids/uuid_create_graphql.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/ids/uuid_create_graphql.rs index afacaa5292b5..f6c6b45530f4 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/ids/uuid_create_graphql.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/ids/uuid_create_graphql.rs @@ -8,8 +8,8 @@ mod uuid_create_graphql { fn schema_1() -> String { let schema = indoc! { r#"model Todo { - #id(id, String, @id, @default(uuid())) - title String + #id(id, String, @id, @default(uuid())) + title String }"# }; @@ -22,11 +22,11 @@ mod uuid_create_graphql { let res = run_query_json!( &runner, r#"mutation { - createOneTodo(data: { title: "the title" }){ - id - title - } - }"# + createOneTodo(data: { title: "the title" }){ + id + title + } + }"# ); insta::assert_snapshot!( @@ -34,12 +34,11 @@ mod uuid_create_graphql { @r###""the title""### ); - let uuid = match &res["data"]["createOneTodo"]["id"] { - serde_json::Value::String(str) => str, - _ => unreachable!(), - }; + let uuid = res["data"]["createOneTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); - uuid::Uuid::parse_str(uuid.as_str()).expect("Expected valid UUID but couldn't parse it."); + uuid::Uuid::parse_str(uuid).expect("Expected valid UUID but couldn't parse it."); Ok(()) } @@ -47,10 +46,10 @@ mod uuid_create_graphql { fn schema_2() -> String { let schema = indoc! { r#"model TableA { - #id(id, String, @id, @default(uuid())) - name String - b String? @unique - }"# + #id(id, String, @id, @default(uuid())) + name String + b String? @unique + }"# }; schema.to_owned() @@ -69,7 +68,7 @@ mod uuid_create_graphql { Ok(()) } - fn schema_3() -> String { + fn schema_uuid_7() -> String { let schema = indoc! { r#"model Todo { #id(id, String, @id, @default(uuid(7))) @@ -81,25 +80,24 @@ mod uuid_create_graphql { } // "Creating an item with an id field of model UUIDv7 and retrieving it" should "work" - #[connector_test(schema(schema_3))] + #[connector_test(schema(schema_uuid_7))] async fn create_uuid_v7_and_retrieve_it_should_work(runner: Runner) -> TestResult<()> { let res = run_query_json!( &runner, r#"mutation { - createOneTodo(data: { title: "the title" }){ - id - } - }"# + createOneTodo(data: { title: "the title" }){ + id + } + }"# ); - let uuid = match &res["data"]["createOneTodo"]["id"] { - serde_json::Value::String(str) => str, - _ => unreachable!(), - }; + let uuid = res["data"]["createOneTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); // Validate that this is a valid UUIDv7 value { - let uuid = uuid::Uuid::parse_str(uuid.as_str()).expect("Expected valid UUID but couldn't parse it."); + let uuid = uuid::Uuid::parse_str(uuid).expect("Expected valid UUID but couldn't parse it."); assert_eq!( uuid.get_version().expect("Expected UUIDv7 but got something else."), uuid::Version::SortRand @@ -111,22 +109,126 @@ mod uuid_create_graphql { &runner, r#"query { findManyTodo(where: { title: "the title" }) { id }}"# ); - if let serde_json::Value::String(str) = &res["data"]["findManyTodo"][0]["id"] { - assert_eq!(str, uuid); - } else { - panic!("Expected UUID but got something else."); - } + let uuid_find_many = res["data"]["findManyTodo"][0]["id"] + .as_str() + .expect("Expected string ID but got something else."); + assert_eq!(uuid_find_many, uuid); // Test findUnique let res = run_query_json!( &runner, format!(r#"query {{ findUniqueTodo(where: {{ id: "{}" }}) {{ id }} }}"#, uuid) ); - if let serde_json::Value::String(str) = &res["data"]["findUniqueTodo"]["id"] { - assert_eq!(str, uuid); - } else { - panic!("Expected UUID but got something else."); - } + let uuid_find_unique = res["data"]["findUniqueTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); + assert_eq!(uuid_find_unique, uuid); + + Ok(()) + } + + fn schema_cuid_1() -> String { + let schema = indoc! { + r#"model Todo { + #id(id, String, @id, @default(cuid(1))) + title String + }"# + }; + + schema.to_owned() + } + + // "Creating an item with an id field of model CUIDv1 and retrieving it" should "work" + #[connector_test(schema(schema_cuid_1))] + async fn create_cuid_v1_and_retrieve_it_should_work(runner: Runner) -> TestResult<()> { + let res = run_query_json!( + &runner, + r#"mutation { + createOneTodo(data: { title: "the title" }){ + id + } + }"# + ); + + let cuid_1: &str = res["data"]["createOneTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); + + // Validate that this is a valid CUIDv1 value + assert!(cuid::is_cuid1(cuid_1)); + + // Test findMany + let res = run_query_json!( + &runner, + r#"query { findManyTodo(where: { title: "the title" }) { id }}"# + ); + let cuid_find_many = res["data"]["findManyTodo"][0]["id"] + .as_str() + .expect("Expected string ID but got something else."); + assert_eq!(cuid_find_many, cuid_1); + + // Test findUnique + let res = run_query_json!( + &runner, + format!(r#"query {{ findUniqueTodo(where: {{ id: "{}" }}) {{ id }} }}"#, cuid_1) + ); + let uuid_find_unique = res["data"]["findUniqueTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); + assert_eq!(uuid_find_unique, cuid_1); + + Ok(()) + } + + fn schema_cuid_2() -> String { + let schema = indoc! { + r#"model Todo { + #id(id, String, @id, @default(cuid(2))) + title String + }"# + }; + + schema.to_owned() + } + + // "Creating an item with an id field of model CUIDv2 and retrieving it" should "work" + #[connector_test(schema(schema_cuid_2))] + async fn create_cuid_v2_and_retrieve_it_should_work(runner: Runner) -> TestResult<()> { + let res = run_query_json!( + &runner, + r#"mutation { + createOneTodo(data: { title: "the title" }){ + id + } + }"# + ); + + let cuid_2 = res["data"]["createOneTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); + + // Validate that this is a valid CUIDv2 value + assert!(cuid::is_cuid2(cuid_2)); + + // Test findMany + let res = run_query_json!( + &runner, + r#"query { findManyTodo(where: { title: "the title" }) { id }}"# + ); + let cuid_find_many = res["data"]["findManyTodo"][0]["id"] + .as_str() + .expect("Expected string ID but got something else."); + assert_eq!(cuid_find_many, cuid_2); + + // Test findUnique + let res = run_query_json!( + &runner, + format!(r#"query {{ findUniqueTodo(where: {{ id: "{}" }}) {{ id }} }}"#, cuid_2) + ); + let cuid_find_unique = res["data"]["findUniqueTodo"]["id"] + .as_str() + .expect("Expected string ID but got something else."); + assert_eq!(cuid_find_unique, cuid_2); Ok(()) } diff --git a/query-engine/connectors/mongodb-query-connector/Cargo.toml b/query-engine/connectors/mongodb-query-connector/Cargo.toml index 0c4cefcce845..f631993f408d 100644 --- a/query-engine/connectors/mongodb-query-connector/Cargo.toml +++ b/query-engine/connectors/mongodb-query-connector/Cargo.toml @@ -21,7 +21,6 @@ tracing-futures.workspace = true uuid.workspace = true indexmap.workspace = true prisma-metrics.path = "../../../libs/metrics" -cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } derive_more.workspace = true [dependencies.query-structure] diff --git a/query-engine/connectors/sql-query-connector/Cargo.toml b/query-engine/connectors/sql-query-connector/Cargo.toml index 2e3e0fe2fe5c..700b35663d5b 100644 --- a/query-engine/connectors/sql-query-connector/Cargo.toml +++ b/query-engine/connectors/sql-query-connector/Cargo.toml @@ -50,7 +50,7 @@ tracing-futures.workspace = true uuid.workspace = true opentelemetry = { version = "0.17", features = ["tokio"] } tracing-opentelemetry = "0.17.3" -cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } +cuid.workspace = true quaint.workspace = true [dev-dependencies] diff --git a/query-engine/core/Cargo.toml b/query-engine/core/Cargo.toml index 6005b091f55a..bef1bb34929a 100644 --- a/query-engine/core/Cargo.toml +++ b/query-engine/core/Cargo.toml @@ -35,7 +35,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-opentelemetry = "0.17.4" user-facing-errors = { path = "../../libs/user-facing-errors" } uuid.workspace = true -cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } +cuid.workspace = true schema = { path = "../schema" } crosstarget-utils = { path = "../../libs/crosstarget-utils" } telemetry = { path = "../../libs/telemetry" } diff --git a/query-engine/core/src/interactive_transactions/mod.rs b/query-engine/core/src/interactive_transactions/mod.rs index 009cab37ccfd..e27275632dc4 100644 --- a/query-engine/core/src/interactive_transactions/mod.rs +++ b/query-engine/core/src/interactive_transactions/mod.rs @@ -16,8 +16,7 @@ pub struct TxId(String); impl Default for TxId { fn default() -> Self { - #[allow(deprecated)] - Self(cuid::cuid().unwrap()) + Self(cuid::cuid2()) } } diff --git a/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs b/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs index a0e120216073..e9403d150cc7 100644 --- a/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs +++ b/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs @@ -296,7 +296,7 @@ fn default_value_to_serde(dv: &DefaultKind) -> serde_json::Value { match dv { DefaultKind::Single(value) => prisma_value_to_serde(&value.clone()), DefaultKind::Expression(vg) => { - let args: Vec<_> = vg.args().iter().map(|(_, v)| v.clone()).collect(); + let args: Vec<_> = vg.args().to_vec(); function_to_serde(vg.name(), &args) } } @@ -310,7 +310,7 @@ fn prisma_value_to_serde(value: &PrismaValue) -> serde_json::Value { PrismaValue::Float(val) => { serde_json::Value::Number(serde_json::Number::from_f64(val.to_f64().unwrap()).unwrap()) } - PrismaValue::Int(val) => serde_json::Value::Number(serde_json::Number::from_f64(*val as f64).unwrap()), + PrismaValue::Int(val) => serde_json::Value::Number(serde_json::Number::from(*val)), PrismaValue::BigInt(val) => serde_json::Value::String(val.to_string()), PrismaValue::DateTime(val) => serde_json::Value::String(val.to_rfc3339()), PrismaValue::Null => serde_json::Value::Null, diff --git a/query-engine/dmmf/src/tests/test-schemas/snapshots/odoo.snapshot.json.gz b/query-engine/dmmf/src/tests/test-schemas/snapshots/odoo.snapshot.json.gz index 9190b3618205..7d1c94c67d19 100644 Binary files a/query-engine/dmmf/src/tests/test-schemas/snapshots/odoo.snapshot.json.gz and b/query-engine/dmmf/src/tests/test-schemas/snapshots/odoo.snapshot.json.gz differ diff --git a/query-engine/dmmf/test_files/functions.json b/query-engine/dmmf/test_files/functions.json index bdc394178a92..7a0f0ee925a4 100644 --- a/query-engine/dmmf/test_files/functions.json +++ b/query-engine/dmmf/test_files/functions.json @@ -36,7 +36,7 @@ "isUpdatedAt": false }, { - "name": "someId", + "name": "someCuid", "kind": "scalar", "isList": false, "isRequired": true, @@ -47,10 +47,143 @@ "type": "String", "default": { "name": "cuid", + "args": [ + 1 + ] + }, + "isGenerated": false, + "isUpdatedAt": false + }, + { + "name": "someCuid1", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "cuid", + "args": [ + 1 + ] + }, + "isGenerated": false, + "isUpdatedAt": false + }, + { + "name": "someCuid2", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "cuid", + "args": [ + 2 + ] + }, + "isGenerated": false, + "isUpdatedAt": false + }, + { + "name": "someUuid", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "uuid", + "args": [ + 4 + ] + }, + "isGenerated": false, + "isUpdatedAt": false + }, + { + "name": "someUuid4", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "uuid", + "args": [ + 4 + ] + }, + "isGenerated": false, + "isUpdatedAt": false + }, + { + "name": "someUuid7", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "uuid", + "args": [ + 7 + ] + }, + "isGenerated": false, + "isUpdatedAt": false + }, + { + "name": "someNanoid", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "nanoid", "args": [] }, "isGenerated": false, "isUpdatedAt": false + }, + { + "name": "someNanoidLen6", + "kind": "scalar", + "isList": false, + "isRequired": true, + "isUnique": true, + "isId": false, + "isReadOnly": false, + "hasDefaultValue": true, + "type": "String", + "default": { + "name": "nanoid", + "args": [ + 6 + ] + }, + "isGenerated": false, + "isUpdatedAt": false } ], "primaryKey": null, @@ -77,7 +210,77 @@ "isDefinedOnField": true, "fields": [ { - "name": "someId" + "name": "someCuid" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someCuid1" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someCuid2" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someUuid" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someUuid4" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someUuid7" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someNanoid" + } + ] + }, + { + "model": "User", + "type": "unique", + "isDefinedOnField": true, + "fields": [ + { + "name": "someNanoidLen6" } ] } diff --git a/query-engine/dmmf/test_files/functions.prisma b/query-engine/dmmf/test_files/functions.prisma index 24c550d79759..f6a453488243 100644 --- a/query-engine/dmmf/test_files/functions.prisma +++ b/query-engine/dmmf/test_files/functions.prisma @@ -1,5 +1,12 @@ model User { - id Int @id - createdAt DateTime @default(now()) - someId String @default(cuid()) @unique + id Int @id + createdAt DateTime @default(now()) + someCuid String @default(cuid()) @unique + someCuid1 String @default(cuid(1)) @unique + someCuid2 String @default(cuid(2)) @unique + someUuid String @default(uuid()) @unique + someUuid4 String @default(uuid(4)) @unique + someUuid7 String @default(uuid(7)) @unique + someNanoid String @default(nanoid()) @unique + someNanoidLen6 String @default(nanoid(6)) @unique } diff --git a/query-engine/driver-adapters/executor/package.json b/query-engine/driver-adapters/executor/package.json index 2b4553ff6946..a0057e8611e5 100644 --- a/query-engine/driver-adapters/executor/package.json +++ b/query-engine/driver-adapters/executor/package.json @@ -1,7 +1,7 @@ { "engines": { - "node": ">=16.13", - "pnpm": ">=8.6.6 <9" + "node": ">=18.18", + "pnpm": ">=8.6.6" }, "name": "executor", "version": "0.0.1", diff --git a/query-engine/query-engine-wasm/rust-toolchain.toml b/query-engine/query-engine-wasm/rust-toolchain.toml index 44e38c0b8707..51d10a458097 100644 --- a/query-engine/query-engine-wasm/rust-toolchain.toml +++ b/query-engine/query-engine-wasm/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-09-01" +channel = "nightly-2024-11-20" components = ["clippy", "rustfmt", "rust-src"] targets = [ "wasm32-unknown-unknown", diff --git a/query-engine/query-structure/Cargo.toml b/query-engine/query-structure/Cargo.toml index 847d97bee2ff..d3bfff0ce8e0 100644 --- a/query-engine/query-structure/Cargo.toml +++ b/query-engine/query-structure/Cargo.toml @@ -10,15 +10,14 @@ prisma-value = { path = "../../libs/prisma-value" } bigdecimal = "0.3" thiserror = "1.0" -getrandom = { version = "0.2" } +getrandom = { workspace = true, optional = true } uuid = { workspace = true, optional = true } -cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support", optional = true } +cuid = { workspace = true, optional = true } nanoid = { version = "0.4.0", optional = true } chrono.workspace = true -[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] -version = "0.2" -features = ["js"] +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true, features = ["js"] } [features] # Support for generating default UUID, CUID, nanoid and datetime values. diff --git a/query-engine/query-structure/src/default_value.rs b/query-engine/query-structure/src/default_value.rs index 605224909e31..0e5d64caf972 100644 --- a/query-engine/query-structure/src/default_value.rs +++ b/query-engine/query-structure/src/default_value.rs @@ -35,7 +35,7 @@ impl DefaultKind { /// Does this match @default(nanoid(_))? pub fn is_nanoid(&self) -> bool { - matches!(self, DefaultKind::Expression(generator) if generator.name.starts_with("nanoid(")) + matches!(self, DefaultKind::Expression(generator) if generator.name == "nanoid") } /// Does this match @default(now())? @@ -45,7 +45,7 @@ impl DefaultKind { /// Does this match @default(uuid(_))? pub fn is_uuid(&self) -> bool { - matches!(self, DefaultKind::Expression(generator) if generator.name.starts_with("uuid")) + matches!(self, DefaultKind::Expression(generator) if generator.name == "uuid") } pub fn unwrap_single(self) -> PrismaValue { @@ -59,9 +59,7 @@ impl DefaultKind { // intended for primary key values! pub fn to_dbgenerated_func(&self) -> Option { match self { - DefaultKind::Expression(ref expr) if expr.is_dbgenerated() => { - expr.args.first().map(|val| val.1.to_string()) - } + DefaultKind::Expression(ref expr) if expr.is_dbgenerated() => expr.args.first().map(|val| val.to_string()), _ => None, } } @@ -147,13 +145,13 @@ impl DefaultValue { #[derive(Clone)] pub struct ValueGenerator { name: String, - args: Vec<(Option, PrismaValue)>, + args: Vec, generator: ValueGeneratorFn, } impl ValueGenerator { - pub fn new(name: String, args: Vec<(Option, PrismaValue)>) -> Result { - let generator = ValueGeneratorFn::new(name.as_ref())?; + pub fn new(name: String, args: Vec) -> Result { + let generator = ValueGeneratorFn::new(name.as_ref(), args.as_ref())?; Ok(ValueGenerator { name, args, generator }) } @@ -162,15 +160,17 @@ impl ValueGenerator { ValueGenerator::new("autoincrement".to_owned(), vec![]).unwrap() } - pub fn new_sequence(args: Vec<(Option, PrismaValue)>) -> Self { + pub fn new_sequence(args: Vec) -> Self { ValueGenerator::new("sequence".to_owned(), args).unwrap() } pub fn new_dbgenerated(description: String) -> Self { + let name = "dbgenerated".to_owned(); + if description.trim_matches('\0').is_empty() { - ValueGenerator::new("dbgenerated".to_owned(), Vec::new()).unwrap() + ValueGenerator::new(name, Vec::new()).unwrap() } else { - ValueGenerator::new("dbgenerated".to_owned(), vec![(None, PrismaValue::String(description))]).unwrap() + ValueGenerator::new(name, vec![PrismaValue::String(description)]).unwrap() } } @@ -182,23 +182,21 @@ impl ValueGenerator { ValueGenerator::new("now".to_owned(), vec![]).unwrap() } - pub fn new_cuid() -> Self { - ValueGenerator::new("cuid".to_owned(), vec![]).unwrap() + pub fn new_cuid(version: u8) -> Self { + ValueGenerator::new("cuid".to_owned(), vec![PrismaValue::Int(version as i64)]).unwrap() } pub fn new_uuid(version: u8) -> Self { - ValueGenerator::new(format!("uuid({version})"), vec![]).unwrap() + ValueGenerator::new("uuid".to_owned(), vec![PrismaValue::Int(version as i64)]).unwrap() } pub fn new_nanoid(length: Option) -> Self { + let name = "nanoid".to_owned(); + if let Some(length) = length { - ValueGenerator::new( - format!("nanoid({length})"), - vec![(None, PrismaValue::Int(length.into()))], - ) - .unwrap() + ValueGenerator::new(name, vec![PrismaValue::Int(length.into())]).unwrap() } else { - ValueGenerator::new("nanoid()".to_owned(), vec![]).unwrap() + ValueGenerator::new(name, vec![]).unwrap() } } @@ -206,7 +204,7 @@ impl ValueGenerator { &self.name } - pub fn args(&self) -> &[(Option, PrismaValue)] { + pub fn args(&self) -> &[PrismaValue] { &self.args } @@ -219,7 +217,7 @@ impl ValueGenerator { return None; } - self.args.first().and_then(|v| v.1.as_string()) + self.args.first().and_then(|v| v.as_string()) } #[cfg(feature = "default_generators")] @@ -239,7 +237,7 @@ impl ValueGenerator { #[derive(Clone, Copy, PartialEq)] pub enum ValueGeneratorFn { Uuid(u8), - Cuid, + Cuid(u8), Nanoid(Option), Now, Autoincrement, @@ -248,17 +246,25 @@ pub enum ValueGeneratorFn { } impl ValueGeneratorFn { - fn new(name: &str) -> std::result::Result { + fn new(name: &str, args: &[PrismaValue]) -> std::result::Result { match name { - "cuid" => Ok(Self::Cuid), - "uuid" | "uuid(4)" => Ok(Self::Uuid(4)), - "uuid(7)" => Ok(Self::Uuid(7)), + "cuid" => match args[..] { + [PrismaValue::Int(version)] => Ok(Self::Cuid(version as u8)), + _ => unreachable!(), + }, + "uuid" => match args[..] { + [PrismaValue::Int(version)] => Ok(Self::Uuid(version as u8)), + _ => unreachable!(), + }, + "nanoid" => match args[..] { + [PrismaValue::Int(length)] => Ok(Self::Nanoid(Some(length as u8))), + _ => Ok(Self::Nanoid(None)), + }, "now" => Ok(Self::Now), "autoincrement" => Ok(Self::Autoincrement), "sequence" => Ok(Self::Autoincrement), "dbgenerated" => Ok(Self::DbGenerated), "auto" => Ok(Self::Auto), - name if name.starts_with("nanoid(") => Ok(Self::Nanoid(name[7..name.len() - 1].parse::().ok())), _ => Err(format!("The function {name} is not a known function.")), } } @@ -267,7 +273,7 @@ impl ValueGeneratorFn { fn invoke(&self) -> Option { match self { Self::Uuid(version) => Some(Self::generate_uuid(*version)), - Self::Cuid => Some(Self::generate_cuid()), + Self::Cuid(version) => Some(Self::generate_cuid(*version)), Self::Nanoid(length) => Some(Self::generate_nanoid(length)), Self::Now => Some(Self::generate_now()), Self::Autoincrement => None, @@ -277,9 +283,12 @@ impl ValueGeneratorFn { } #[cfg(feature = "default_generators")] - fn generate_cuid() -> PrismaValue { - #[allow(deprecated)] - PrismaValue::String(cuid::cuid().unwrap()) + fn generate_cuid(version: u8) -> PrismaValue { + PrismaValue::String(match version { + 1 => cuid::cuid1(), + 2 => cuid::cuid2(), + _ => panic!("Unknown `cuid` version: {}", version), + }) } #[cfg(feature = "default_generators")] @@ -358,8 +367,16 @@ mod tests { } #[test] - fn default_value_is_cuid() { - let cuid_default = DefaultValue::new_expression(ValueGenerator::new_cuid()); + fn default_value_is_cuidv1() { + let cuid_default = DefaultValue::new_expression(ValueGenerator::new_cuid(1)); + + assert!(cuid_default.is_cuid()); + assert!(!cuid_default.is_now()); + } + + #[test] + fn default_value_is_cuidv2() { + let cuid_default = DefaultValue::new_expression(ValueGenerator::new_cuid(2)); assert!(cuid_default.is_cuid()); assert!(!cuid_default.is_now()); diff --git a/query-engine/query-structure/src/field/scalar.rs b/query-engine/query-structure/src/field/scalar.rs index 5fc10acddd13..8027715a3f9c 100644 --- a/query-engine/query-structure/src/field/scalar.rs +++ b/query-engine/query-structure/src/field/scalar.rs @@ -1,6 +1,7 @@ use crate::{ast, parent_container::ParentContainer, prelude::*, DefaultKind, NativeTypeInstance, ValueGenerator}; use chrono::{DateTime, FixedOffset}; use psl::{ + generators::{DEFAULT_CUID_VERSION, DEFAULT_UUID_VERSION}, parser_database::{self as db, walkers, ScalarFieldType, ScalarType}, schema_ast::ast::FieldArity, }; @@ -250,12 +251,18 @@ pub fn dml_default_kind(default_value: &ast::Expression, scalar_type: Option().unwrap()) - .unwrap_or(4); + .unwrap_or(DEFAULT_UUID_VERSION); DefaultKind::Expression(ValueGenerator::new_uuid(version)) } - ast::Expression::Function(funcname, _args, _) if funcname == "cuid" => { - DefaultKind::Expression(ValueGenerator::new_cuid()) + ast::Expression::Function(funcname, args, _) if funcname == "cuid" => { + let version = args + .arguments + .first() + .and_then(|arg| arg.value.as_numeric_value()) + .map(|(val, _)| val.parse::().unwrap()) + .unwrap_or(DEFAULT_CUID_VERSION); + DefaultKind::Expression(ValueGenerator::new_cuid(version)) } ast::Expression::Function(funcname, args, _) if funcname == "nanoid" => { DefaultKind::Expression(ValueGenerator::new_nanoid( diff --git a/query-engine/query-structure/tests/datamodel_converter_tests.rs b/query-engine/query-structure/tests/datamodel_converter_tests.rs index 31f00976378e..8a84b74b2622 100644 --- a/query-engine/query-structure/tests/datamodel_converter_tests.rs +++ b/query-engine/query-structure/tests/datamodel_converter_tests.rs @@ -333,6 +333,32 @@ fn uuid_fields_must_work() { "#, ); + let model = datamodel.assert_model("Test"); + model + .assert_scalar_field("id") + .assert_type_identifier(TypeIdentifier::String); + + let datamodel = convert( + r#" + model Test { + id String @id @default(uuid(4)) + } + "#, + ); + + let model = datamodel.assert_model("Test"); + model + .assert_scalar_field("id") + .assert_type_identifier(TypeIdentifier::String); + + let datamodel = convert( + r#" + model Test { + id String @id @default(uuid(7)) + } + "#, + ); + let model = datamodel.assert_model("Test"); model .assert_scalar_field("id") @@ -349,6 +375,32 @@ fn cuid_fields_must_work() { "#, ); + let model = datamodel.assert_model("Test"); + model + .assert_scalar_field("id") + .assert_type_identifier(TypeIdentifier::String); + + let datamodel = convert( + r#" + model Test { + id String @id @default(cuid(1)) + } + "#, + ); + + let model = datamodel.assert_model("Test"); + model + .assert_scalar_field("id") + .assert_type_identifier(TypeIdentifier::String); + + let datamodel = convert( + r#" + model Test { + id String @id @default(cuid(2)) + } + "#, + ); + let model = datamodel.assert_model("Test"); model .assert_scalar_field("id") diff --git a/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/default.rs b/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/default.rs index dcb04371439b..e19a946ad5a4 100644 --- a/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/default.rs +++ b/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/default.rs @@ -14,8 +14,8 @@ pub(crate) enum DefaultKind<'a> { Sequence(&'a sql::postgres::Sequence), DbGenerated(Option<&'a str>), Autoincrement, - Uuid, - Cuid, + Uuid(Option), + Cuid(Option), Nanoid(Option), Now, String(&'a str), @@ -116,8 +116,24 @@ impl<'a> DefaultValuePair<'a> { }, (None, sql::ColumnTypeFamily::String | sql::ColumnTypeFamily::Uuid) => match self.previous { - Some(previous) if previous.is_cuid() => Some(DefaultKind::Cuid), - Some(previous) if previous.is_uuid() => Some(DefaultKind::Uuid), + Some(previous) if previous.is_cuid() => { + let version = previous.value().as_function().and_then(|(_, args, _)| { + args.arguments + .first() + .map(|arg| arg.value.as_numeric_value().unwrap().0.parse::().unwrap()) + }); + + Some(DefaultKind::Cuid(version)) + } + Some(previous) if previous.is_uuid() => { + let version = previous.value().as_function().and_then(|(_, args, _)| { + args.arguments + .first() + .map(|arg| arg.value.as_numeric_value().unwrap().0.parse::().unwrap()) + }); + + Some(DefaultKind::Uuid(version)) + } Some(previous) if previous.is_nanoid() => { let length = previous.value().as_function().and_then(|(_, args, _)| { args.arguments diff --git a/schema-engine/connectors/sql-schema-connector/src/introspection/rendering/defaults.rs b/schema-engine/connectors/sql-schema-connector/src/introspection/rendering/defaults.rs index ad8ef45e192d..748cf77c6883 100644 --- a/schema-engine/connectors/sql-schema-connector/src/introspection/rendering/defaults.rs +++ b/schema-engine/connectors/sql-schema-connector/src/introspection/rendering/defaults.rs @@ -45,8 +45,24 @@ pub(crate) fn render(default: DefaultValuePair<'_>) -> Option Some(renderer::DefaultValue::function(Function::new("autoincrement"))), - DefaultKind::Uuid => Some(renderer::DefaultValue::function(Function::new("uuid"))), - DefaultKind::Cuid => Some(renderer::DefaultValue::function(Function::new("cuid"))), + DefaultKind::Uuid(version) => { + let mut fun = Function::new("uuid"); + + if let Some(version) = version { + fun.push_param(Value::from(Constant::from(version))); + } + + Some(renderer::DefaultValue::function(fun)) + } + DefaultKind::Cuid(version) => { + let mut fun = Function::new("cuid"); + + if let Some(version) = version { + fun.push_param(Value::from(Constant::from(version))); + } + + Some(renderer::DefaultValue::function(fun)) + } DefaultKind::Nanoid(length) => { let mut fun = Function::new("nanoid"); diff --git a/schema-engine/sql-introspection-tests/tests/re_introspection/mod.rs b/schema-engine/sql-introspection-tests/tests/re_introspection/mod.rs index a7c6e1897b95..f4b23e31d8d0 100644 --- a/schema-engine/sql-introspection-tests/tests/re_introspection/mod.rs +++ b/schema-engine/sql-introspection-tests/tests/re_introspection/mod.rs @@ -969,16 +969,19 @@ async fn multiple_changed_relation_names_due_to_mapped_models(api: &mut TestApi) } #[test_connector(tags(Postgres), exclude(CockroachDb))] -async fn virtual_cuid_default(api: &mut TestApi) { +async fn virtual_uid_default(api: &mut TestApi) { api.barrel() .execute(|migration| { migration.create_table("User", |t| { t.add_column("id", types::varchar(30).primary(true)); - t.add_column("non_id", types::varchar(30)); + t.add_column("non_id_1", types::varchar(30)); + t.add_column("non_id_2", types::varchar(30)); }); migration.create_table("User2", |t| { t.add_column("id", types::varchar(36).primary(true)); + t.add_column("non_id_1", types::varchar(36)); + t.add_column("non_id_2", types::varchar(36)); }); migration.create_table("User3", |t| { @@ -995,11 +998,14 @@ async fn virtual_cuid_default(api: &mut TestApi) { let input_dm = r#" model User { id String @id @default(cuid()) @db.VarChar(30) - non_id String @default(cuid()) @db.VarChar(30) + non_id_1 String @default(cuid(1)) @db.VarChar(30) + non_id_2 String @default(cuid(2)) @db.VarChar(30) } model User2 { id String @id @default(uuid()) @db.VarChar(36) + non_id_1 String @default(uuid(4)) @db.VarChar(36) + non_id_2 String @default(uuid(7)) @db.VarChar(36) } model User3 { @@ -1010,11 +1016,14 @@ async fn virtual_cuid_default(api: &mut TestApi) { let final_dm = indoc! {r#" model User { id String @id @default(cuid()) @db.VarChar(30) - non_id String @default(cuid()) @db.VarChar(30) + non_id_1 String @default(cuid(1)) @db.VarChar(30) + non_id_2 String @default(cuid(2)) @db.VarChar(30) } model User2 { id String @id @default(uuid()) @db.VarChar(36) + non_id_1 String @default(uuid(4)) @db.VarChar(36) + non_id_2 String @default(uuid(7)) @db.VarChar(36) } model User3 {