Skip to content
This repository was archived by the owner on Sep 2, 2022. It is now read-only.

Commit a1bed6a

Browse files
authored
Merge pull request #4140 from prisma/MongoUniqueRegression
Mongo Updating a Field to Unique Bug
2 parents a1b7a10 + a7261de commit a1bed6a

11 files changed

Lines changed: 64 additions & 27 deletions

File tree

server/connectors/deploy-connector-mongo/src/main/scala/com/prisma/deploy/connector/mongo/database/MongoDeployDatabaseMutationBuilder.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,14 @@ object MongoDeployDatabaseMutationBuilder {
9191
}
9292

9393
//Fields
94-
def createField(model: Model, fieldName: String) = DeployMongoAction { database =>
94+
def createIndex(model: Model, fieldName: String) = DeployMongoAction { database =>
9595
model.isEmbedded match {
9696
case false => addUniqueConstraint(database, model.dbName, fieldName)
9797
case true => Future.successful(())
9898
}
9999
}
100100

101-
def deleteField(model: Model, fieldName: String) = DeployMongoAction { database =>
101+
def deleteIndex(model: Model, fieldName: String) = DeployMongoAction { database =>
102102
model.isEmbedded match {
103103
case false => removeUniqueConstraint(database, model.dbName, fieldName)
104104
case true => Future.successful(())

server/connectors/deploy-connector-mongo/src/main/scala/com/prisma/deploy/connector/mongo/impl/MongoClientDbQueries.scala

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.prisma.deploy.connector.mongo.impl
22

33
import com.prisma.deploy.connector.ClientDbQueries
4-
import com.prisma.deploy.connector.mongo.database.MongoDeployDatabaseQueryBuilder
54
import com.prisma.shared.models.RelationSide.RelationSide
65
import com.prisma.shared.models._
7-
import org.mongodb.scala.MongoClient
6+
import org.mongodb.scala.{Document, MongoClient}
7+
import org.mongodb.scala.model.Aggregates.{project => mongoProjection, `match`, limit, group}
8+
import org.mongodb.scala.model.Accumulators._
9+
import org.mongodb.scala.model.Filters._
10+
import org.mongodb.scala.model.Projections._
811

912
import scala.concurrent.{ExecutionContext, Future}
1013

@@ -34,8 +37,20 @@ case class MongoClientDbQueries(project: Project, clientDatabase: MongoClient)(i
3437
}
3538

3639
def existsDuplicateValueByModelAndField(model: Model, field: ScalarField): Future[Boolean] = {
37-
// val query = MongoDeployDatabaseQueryBuilder.existsDuplicateValueByModelAndField(project.id, model.name, field.name)
38-
Future.successful(false)
40+
clientDatabase
41+
.getDatabase(database)
42+
.getCollection(model.dbName)
43+
.aggregate(
44+
Seq(
45+
`match`(notEqual(field.dbName, null)),
46+
group(s"$$${field.dbName}", sum("count", 1)),
47+
`match`(gt("count", 1)),
48+
mongoProjection(include("_id")),
49+
limit(1)
50+
)
51+
)
52+
.toFuture()
53+
.map(_.nonEmpty)
3954
}
4055

4156
override def enumValueIsInUse(models: Vector[Model], enumName: String, value: String): Future[Boolean] = {

server/connectors/deploy-connector-mongo/src/main/scala/com/prisma/deploy/connector/mongo/impl/mutactions/ColumnMutactionInterpreters.scala

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,38 @@ import com.prisma.deploy.connector.mongo.database.{MongoDeployDatabaseMutationBu
55

66
object CreateColumnInterpreter extends MongoMutactionInterpreter[CreateColumn] {
77
override def execute(mutaction: CreateColumn) = mutaction.field.isUnique && !mutaction.field.isId match {
8-
case true => MongoDeployDatabaseMutationBuilder.createField(mutaction.model, mutaction.field.dbName)
8+
case true => MongoDeployDatabaseMutationBuilder.createIndex(mutaction.model, mutaction.field.dbName)
99
case false => NoAction.unit
1010
}
1111

1212
override def rollback(mutaction: CreateColumn) = mutaction.field.isUnique && !mutaction.field.isId match {
13-
case true => MongoDeployDatabaseMutationBuilder.deleteField(mutaction.model, mutaction.field.dbName)
13+
case true => MongoDeployDatabaseMutationBuilder.deleteIndex(mutaction.model, mutaction.field.dbName)
1414
case false => NoAction.unit
1515
}
1616
}
1717

1818
object DeleteColumnInterpreter extends MongoMutactionInterpreter[DeleteColumn] {
1919
override def execute(mutaction: DeleteColumn) = mutaction.field.isUnique && !mutaction.field.isId match {
20-
case true => MongoDeployDatabaseMutationBuilder.deleteField(mutaction.model, mutaction.field.dbName)
20+
case true => MongoDeployDatabaseMutationBuilder.deleteIndex(mutaction.model, mutaction.field.dbName)
2121
case false => NoAction.unit
2222
}
2323

2424
override def rollback(mutaction: DeleteColumn) = mutaction.field.isUnique && !mutaction.field.isId match {
25-
case true => MongoDeployDatabaseMutationBuilder.createField(mutaction.model, mutaction.field.dbName)
25+
case true => MongoDeployDatabaseMutationBuilder.createIndex(mutaction.model, mutaction.field.dbName)
2626
case false => NoAction.unit
2727
}
2828
}
2929

30-
//Fixme index names cannot be changed in Mongo/ This is a problem after renames since we derive the index name from modelname and fieldname
3130
object UpdateColumnInterpreter extends MongoMutactionInterpreter[UpdateColumn] {
32-
override def execute(mutaction: UpdateColumn) = {
33-
NoAction.unit //Fixme -> add/remove unique index
31+
override def execute(mutaction: UpdateColumn) = (mutaction.oldField.isUnique, mutaction.newField.isUnique) match {
32+
case (false, true) => MongoDeployDatabaseMutationBuilder.createIndex(mutaction.model, mutaction.newField.dbName)
33+
case (true, false) => MongoDeployDatabaseMutationBuilder.deleteIndex(mutaction.model, mutaction.newField.dbName)
34+
case _ => NoAction.unit
3435
}
3536

36-
override def rollback(mutaction: UpdateColumn) = {
37-
NoAction.unit //Fixme -> remove/add unique index
37+
override def rollback(mutaction: UpdateColumn) = (mutaction.oldField.isUnique, mutaction.newField.isUnique) match {
38+
case (false, true) => MongoDeployDatabaseMutationBuilder.deleteIndex(mutaction.model, mutaction.newField.dbName)
39+
case (true, false) => MongoDeployDatabaseMutationBuilder.createIndex(mutaction.model, mutaction.newField.dbName)
40+
case _ => NoAction.unit
3841
}
3942
}

server/connectors/deploy-connector/src/main/scala/com/prisma/deploy/connector/MigrationStepMapperImpl.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ case class MigrationStepMapperImpl(project: Project) extends MigrationStepMapper
7070
case _ if previous.isScalarList && next.isScalarList => Vector(deleteScalarListTable, createScalarListTable)
7171
case _ if previous.isScalarNonList && next.isScalarNonList =>
7272
val isIdTypeChange = previous.asScalarField_!.isId && next.asScalarField_!.isId && previous.asScalarField_!.typeIdentifier != next.asScalarField_!.typeIdentifier
73-
val common = Vector(createTemporaryColumn, deleteColumn, renameTemporaryColumn) // a table might have temporary no columns. MySQL does not allow this. //Fixme this breaks for SQLITE
73+
val common = Vector(createTemporaryColumn, deleteColumn, renameTemporaryColumn) // a table might have temporary no columns. MySQL does not allow this.
7474
if (isIdTypeChange) {
7575
val deleteRelations = previousSchema.relations.filter(_.containsTheModel(previous.model)).map(deleteRelation).toVector
7676
val recreateRelations = nextSchema.relations

server/integration-tests/integration-tests-mysql/src/test/scala/com/prisma/integration/AddingOptionalBackRelationDuringMigrationSpec.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.prisma.integration
22

3+
import com.prisma.ConnectorTag
4+
import com.prisma.ConnectorTag.{MySqlConnectorTag, PostgresConnectorTag, SQLiteConnectorTag}
35
import org.scalatest.{FlatSpec, Matchers}
46

57
class AddingOptionalBackRelationDuringMigrationSpec extends FlatSpec with Matchers with IntegrationBaseSpec {
8+
override def runOnlyForConnectors: Set[ConnectorTag] = Set(PostgresConnectorTag, SQLiteConnectorTag, MySqlConnectorTag)
69

710
"Adding a missing back-relation of non-list type" should "work when there are no violating occurences of Team" in {
811

server/integration-tests/integration-tests-mysql/src/test/scala/com/prisma/integration/ChangingModelsOfRelationsSpec.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package com.prisma.integration
22

3+
import com.prisma.IgnoreMongo
34
import org.scalatest.{FlatSpec, Matchers}
45

56
class ChangingModelsOfRelationsSpec extends FlatSpec with Matchers with IntegrationBaseSpec {
67

7-
"Changing the model a relation points to" should "delete the existing relation data" in {
8+
"Changing the model a relation points to" should "delete the existing relation data" taggedAs (IgnoreMongo) in {
89

910
val schema =
1011
"""type A {
@@ -81,7 +82,7 @@ class ChangingModelsOfRelationsSpec extends FlatSpec with Matchers with Integrat
8182
as.toString should be("""{"data":{"as":[{"a":"A","b":null}]}}""")
8283
}
8384

84-
"Renaming a model with @rename but keeping its relation" should "work" in {
85+
"Renaming a model with @rename but keeping its relation" should "work" taggedAs (IgnoreMongo) in {
8586

8687
val schema =
8788
"""type A {

server/integration-tests/integration-tests-mysql/src/test/scala/com/prisma/integration/IntegrationBaseSpec.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@ package com.prisma.integration
22

33
import akka.actor.ActorSystem
44
import akka.stream.ActorMaterializer
5+
import com.prisma.ConnectorAwareTest
56
import com.prisma.api.connector.DataResolver
67
import com.prisma.api.util.StringMatchers
78
import com.prisma.api.{ApiTestServer, TestApiDependenciesImpl}
8-
import com.prisma.deploy.specutils.{TestDeployDependencies, DeployTestServer}
9-
import com.prisma.shared.models.{Migration, Project}
9+
import com.prisma.config.PrismaConfig
10+
import com.prisma.deploy.specutils.{DeployTestServer, TestDeployDependencies}
11+
import com.prisma.shared.models.{ConnectorCapabilities, Migration, Project}
1012
import com.prisma.utils.await.AwaitUtils
1113
import com.prisma.utils.json.PlayJsonExtensions
12-
import cool.graph.cuid.Cuid
1314
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, Suite}
1415
import play.api.libs.json.JsString
1516

1617
import scala.collection.mutable.ArrayBuffer
1718

18-
trait IntegrationBaseSpec extends BeforeAndAfterEach with BeforeAndAfterAll with PlayJsonExtensions with AwaitUtils with StringMatchers { self: Suite =>
19+
trait IntegrationBaseSpec
20+
extends BeforeAndAfterEach
21+
with BeforeAndAfterAll
22+
with PlayJsonExtensions
23+
with AwaitUtils
24+
with StringMatchers
25+
with ConnectorAwareTest { self: Suite =>
1926

2027
implicit lazy val system = ActorSystem()
2128
implicit lazy val materializer = ActorMaterializer()
@@ -34,6 +41,9 @@ trait IntegrationBaseSpec extends BeforeAndAfterEach with BeforeAndAfterAll with
3441

3542
def dataResolver(project: Project): DataResolver = apiTestDependencies.dataResolver(project)
3643

44+
override def capabilities: ConnectorCapabilities = apiTestDependencies.apiConnector.capabilities
45+
46+
override def prismaConfig: PrismaConfig = apiTestDependencies.config
3747
// DEPLOY
3848

3949
implicit lazy val deployTestDependencies: TestDeployDependencies = TestDeployDependencies()

server/integration-tests/integration-tests-mysql/src/test/scala/com/prisma/integration/RenamingWithExistingDataSpec.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.prisma.integration
22

3+
import com.prisma.ConnectorTag.{MySqlConnectorTag, PostgresConnectorTag, SQLiteConnectorTag}
34
import org.scalatest.{FlatSpec, Matchers}
45

56
class RenamingWithExistingDataSpec extends FlatSpec with Matchers with IntegrationBaseSpec {
7+
override def runOnlyForConnectors = Set(PostgresConnectorTag, MySqlConnectorTag, SQLiteConnectorTag)
68

79
"Renaming a model" should "work" in {
810

server/integration-tests/integration-tests-mysql/src/test/scala/com/prisma/integration/SeveralRelationsBetweenSameModelsIntegrationSpec.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.prisma.integration
22

3+
import com.prisma.IgnoreMongo
34
import org.scalatest.{FlatSpec, Matchers}
45

56
class SeveralRelationsBetweenSameModelsIntegrationSpec extends FlatSpec with Matchers with IntegrationBaseSpec {
@@ -243,7 +244,7 @@ class SeveralRelationsBetweenSameModelsIntegrationSpec extends FlatSpec with Mat
243244
deployServer.deploySchemaThatMustErrorWithCode(project, schema1, errorCode = 3018)
244245
}
245246

246-
"Going from two named relations between the same models to one named one without a backrelation" should "work" in {
247+
"Going from two named relations between the same models to one named one without a backrelation" should "work" taggedAs (IgnoreMongo) in {
247248

248249
val schema =
249250
"""type A {
@@ -290,7 +291,7 @@ class SeveralRelationsBetweenSameModelsIntegrationSpec extends FlatSpec with Mat
290291
unchangedRelationContent.toString should be("""{"data":{"as":[{"title":"A1","b":{"title":"B1"}}]}}""")
291292
}
292293

293-
"Going from two named relations between the same models to one named one without a backrelation" should "work even when there is a rename" in {
294+
"Going from two named relations between the same models to one named one without a backrelation" should "work even when there is a rename" taggedAs (IgnoreMongo) in {
294295

295296
val schema =
296297
"""type A {

server/integration-tests/integration-tests-mysql/src/test/scala/com/prisma/integration/deploychecks/UpdateFieldDeploySpec.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.prisma.integration.deploychecks
22

3+
import com.prisma.IgnoreMongo
34
import com.prisma.integration.IntegrationBaseSpec
45
import org.scalatest.{FlatSpec, Matchers}
56

@@ -276,7 +277,7 @@ class UpdateFieldDeploySpec extends FlatSpec with Matchers with IntegrationBaseS
276277
deployServer.deploySchemaThatMustSucceed(project, schema2, 3)
277278
}
278279

279-
"Updating a scalar field to required" should "throw an error if a newly required field is null" in {
280+
"Updating a scalar field to required" should "throw an error if a newly required field is null" taggedAs (IgnoreMongo) in {
280281

281282
val schema =
282283
"""|type A {
@@ -380,7 +381,7 @@ class UpdateFieldDeploySpec extends FlatSpec with Matchers with IntegrationBaseS
380381
deployServer.deploySchemaThatMustSucceed(project, schema2, 3)
381382
}
382383

383-
"Updating a relation field to required" should "throw an error if a newly required field is null" in {
384+
"Updating a relation field to required" should "throw an error if a newly required field is null" taggedAs (IgnoreMongo) in {
384385

385386
val schema =
386387
"""|type A {

0 commit comments

Comments
 (0)