Skip to content

Commit

Permalink
createMany operation (prisma#1550)
Browse files Browse the repository at this point in the history
* Add feature flag.

* Top level schema skeleton

* create -> createOne module

* Basic top level + nested type integration.

* Prohibit createMany nested calls in non-leaf scenarios.

* SQL connector interface skeleton

* Some batching split logic.

* CreateMany support for interpreter <> connector.

* Query graph integration

* Top level createMany.

* Remove createMany for SQLite.
Remove ability to write autoincrement fields for SQL server.

* Top level createMany working.

* Basic nested query integration

* Intermediate commit

* Nested createMany.

* Test "fixes"

* Revert accidental change.

* Fix tests.

* Fix mssql tests
  • Loading branch information
dpetrick authored Jan 28, 2021
1 parent 0912dc6 commit 010a061
Show file tree
Hide file tree
Showing 43 changed files with 1,136 additions and 88 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions libs/datamodel/connectors/datamodel-connector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ pub enum ConnectorCapability {
RelationFieldsInArbitraryOrder,
// start of Query Engine Capabilities
InsensitiveFilters,
CreateMany,
WritableAutoincField,
CreateSkipDuplicates,
}

/// Contains all capabilities that the connector is able to serve.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl MsSqlDatamodelConnector {
ConnectorCapability::AutoIncrementAllowedOnNonId,
ConnectorCapability::AutoIncrementMultipleAllowed,
ConnectorCapability::AutoIncrementNonIndexedAllowed,
ConnectorCapability::CreateMany,
];

let constructors: Vec<NativeTypeConstructor> = vec![
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ impl MySqlDatamodelConnector {
ConnectorCapability::MultipleIndexesWithSameName,
ConnectorCapability::AutoIncrementAllowedOnNonId,
ConnectorCapability::RelationFieldsInArbitraryOrder,
ConnectorCapability::CreateMany,
ConnectorCapability::WritableAutoincField,
ConnectorCapability::CreateSkipDuplicates,
];

let int = NativeTypeConstructor::without_args(INT_TYPE_NAME, vec![ScalarType::Int]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ impl PostgresDatamodelConnector {
ConnectorCapability::AutoIncrementNonIndexedAllowed,
ConnectorCapability::InsensitiveFilters,
ConnectorCapability::RelationFieldsInArbitraryOrder,
ConnectorCapability::CreateMany,
ConnectorCapability::WritableAutoincField,
ConnectorCapability::CreateSkipDuplicates,
];

let small_int = NativeTypeConstructor::without_args(SMALL_INT_TYPE_NAME, vec![ScalarType::Int]);
Expand Down
3 changes: 2 additions & 1 deletion libs/datamodel/core/src/common/preview_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TRANSACTION_API: &str = "transactionApi";
const NATIVE_TYPES: &str = "nativeTypes";
const SQL_SERVER: &str = "microsoftSqlServer";
const GROUP_BY: &str = "groupBy";
const CREATE_MANY: &str = "createMany";

// deprecated preview features
const ATOMIC_NUMBER_OPERATIONS: &str = "atomicNumberOperations";
Expand All @@ -16,7 +17,7 @@ const UNCHECKED_SCALAR_INPUTS: &str = "uncheckedScalarInputs";

pub const DATASOURCE_PREVIEW_FEATURES: &[&str] = &[];

pub const GENERATOR_PREVIEW_FEATURES: &[&str] = &[NATIVE_TYPES, SQL_SERVER, GROUP_BY];
pub const GENERATOR_PREVIEW_FEATURES: &[&str] = &[NATIVE_TYPES, SQL_SERVER, GROUP_BY, CREATE_MANY];

pub const DEPRECATED_GENERATOR_PREVIEW_FEATURES: &[&str] = &[
ATOMIC_NUMBER_OPERATIONS,
Expand Down
3 changes: 2 additions & 1 deletion libs/feature-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ macro_rules! flags {

// `microsoftSqlServer`: Support for Microsoft SQL Server databases
// `groupBy`: Group-By aggregations in the QE.
flags!(microsoftSqlServer, groupBy);
// `createMany`: Create many (bulk insert) API operation.
flags!(microsoftSqlServer, groupBy, createMany);

/// Initializes the feature flags with given flags.
/// Noop if already initialized.
Expand Down
9 changes: 9 additions & 0 deletions libs/prisma-models/src/sql_ext/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ impl AsTable for Model {
fn as_table(&self) -> Table<'static> {
let table: Table<'static> = (self.internal_data_model().db_name.clone(), self.db_name().to_string()).into();

// Todo: Check with Julius
let id_cols: Vec<Column<'static>> = self
.primary_identifier()
.scalar_fields()
.map(|sf| sf.as_column())
.collect();

let table = table.add_unique_index(id_cols);

self.unique_indexes().into_iter().fold(table, |table, index| {
let index: Vec<Column<'static>> = index.fields().iter().map(AsColumn::as_column).collect();
table.add_unique_index(index)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package writes.nestedMutations.notUsingSchemaBase

import org.scalatest.{FlatSpec, Matchers}
import util._

class NestedCreateManyMutationSpec extends FlatSpec with Matchers with ApiSpecBase with SchemaBaseV11 {
val project = ProjectDsl.fromString {
"""
|model ModelA {
| id Int @id
| bs ModelB[]
|}
|
|model ModelB {
| id Int @id
| str1 String
| str2 String?
| str3 String? @default("SOME_DEFAULT")
| a_id Int?
| a ModelA? @relation(fields: [a_id], references: [id])
|}
|""".stripMargin
}

override def beforeEach: Unit = {
database.setup(project)
database.truncateProjectTables(project)
super.beforeEach()
}

"A basic createMany on a create top level" should "work" taggedAs IgnoreSQLite in {
val result = server.query(
"""
|mutation {
| createOneModelA(data: {
| id: 1,
| bs: {
| createMany: {
| data: [
| { id: 1, str1: "1", str2: "1", str3: "1"},
| { id: 2, str1: "2", str3: null},
| { id: 3, str1: "1"},
| ]
| }
| }
| }) {
| bs {
| id
| }
| }
|}
""".stripMargin,
project,
legacy = false
)

result.toString() should be("""{"data":{"createOneModelA":{"bs":[{"id":1},{"id":2},{"id":3}]}}}""")
}

"Nested createMany" should "error on duplicates by default" taggedAs IgnoreSQLite in {
server.queryThatMustFail(
"""
|mutation {
| createOneModelA(data: {
| id: 1,
| bs: {
| createMany: {
| data: [
| { id: 1, str1: "1", str2: "1", str3: "1"},
| { id: 1, str1: "2", str3: null},
| ]
| }
| }
| }) {
| bs {
| id
| }
| }
|}
""".stripMargin,
project,
errorCode = 2002,
errorContains = "UniqueConstraintViolation",
legacy = false
)
}

"Nested createMany" should "not error on duplicates with skipDuplicates true" taggedAs (IgnoreSQLite, IgnoreMsSql) in {
val result = server.query(
"""
|mutation {
| createOneModelA(data: {
| id: 1,
| bs: {
| createMany: {
| skipDuplicates: true,
| data: [
| { id: 1, str1: "1", str2: "1", str3: "1"},
| { id: 1, str1: "2", str3: null},
| ]
| }
| }
| }) {
| bs {
| id
| }
| }
|}
""".stripMargin,
project,
legacy = false
)

result.toString() should be("""{"data":{"createOneModelA":{"bs":[{"id":1}]}}}""")
}

// Note: Checks were originally higher, but test method (command line args) blows up...
// Covers: Batching by row number.
// Each DB allows a certain amount of params per single query, and a certain number of rows.
// We create 1000 nested records.
"Nested createMany" should "allow creating a large number of records (horizontal partitioning check)" taggedAs IgnoreSQLite in {
val records: Seq[String] = for (i <- 1 to 1000) yield { s"""{ id: $i, str1: "$i" }""" }
server.query(
s"""
|mutation {
| createOneModelA(data: {
| id: 1
| bs: {
| createMany: {
| data: [${records.mkString(",")}]
| }
| }
| }) {
| id
| }
|}
""".stripMargin,
project,
legacy = false
)

val result = server.query(
s"""
|{
| aggregateModelB {
| count {
| _all
| }
| }
|}
""".stripMargin,
project,
legacy = false
)

result.toString() should be("""{"data":{"aggregateModelB":{"count":{"_all":1000}}}}""")
}
}
Loading

0 comments on commit 010a061

Please sign in to comment.