Repository: julioz/FloorPlan
Pull request #10 (2020-04-04 16:43:35)

Define foreign key referential actions
According to DBML's spec: https://www.dbml.org/docs/#relationship-settings


Patch:
diff --git a/src/main/kotlin/com/zynger/floorplan/dbml/Reference.kt b/src/main/kotlin/com/zynger/floorplan/dbml/Reference.kt
index 4e40373..f8b12f2 100644
--- a/src/main/kotlin/com/zynger/floorplan/dbml/Reference.kt
+++ b/src/main/kotlin/com/zynger/floorplan/dbml/Reference.kt
@@ -1,6 +1,7 @@
 package com.zynger.floorplan.dbml
 
 import com.zynger.floorplan.model.ForeignKey
+import com.zynger.floorplan.model.ForeignKeyAction
 import java.lang.StringBuilder
 
 class Reference(
@@ -15,6 +16,8 @@ class Reference(
         val fromColumn = foreignKey.columns.first()
         val toTable = foreignKey.table
         val toColumn = foreignKey.referencedColumns.first()
+        val deleteAction = foreignKey.onDelete.toDbml()
+        val updateAction = foreignKey.onUpdate.toDbml()
         return StringBuilder("Ref: ")
             .append(fromTable)
             .append(".")
@@ -23,6 +26,23 @@ class Reference(
             .append(toTable)
             .append(".")
             .append(toColumn)
+            .append(" [")
+            .append("delete: ")
+            .append(deleteAction)
+            .append(", ")
+            .append("update: ")
+            .append(updateAction)
+            .append("]")
             .toString()
     }
-}
\ No newline at end of file
+
+    private fun ForeignKeyAction.toDbml(): String {
+        return when (this) {
+            ForeignKeyAction.NO_ACTION -> "no action"
+            ForeignKeyAction.RESTRICT -> "restrict"
+            ForeignKeyAction.SET_NULL -> "set null"
+            ForeignKeyAction.SET_DEFAULT -> "set default"
+            ForeignKeyAction.CASCADE -> "cascade"
+        }
+    }
+}
diff --git a/src/main/kotlin/com/zynger/floorplan/model/ForeignKey.kt b/src/main/kotlin/com/zynger/floorplan/model/ForeignKey.kt
index 5e4dd0e..3cae7c0 100644
--- a/src/main/kotlin/com/zynger/floorplan/model/ForeignKey.kt
+++ b/src/main/kotlin/com/zynger/floorplan/model/ForeignKey.kt
@@ -1,5 +1,6 @@
 package com.zynger.floorplan.model
 
+import com.zynger.floorplan.model.serializer.ForeignKeyActionSerializer
 import kotlinx.serialization.Serializable
 
 @Serializable
@@ -7,6 +8,18 @@ data class ForeignKey (
     val table: String,
     val columns: List<String>,
     val referencedColumns: List<String>,
-    val onDelete: String,
-    val onUpdate: String
-)
\ No newline at end of file
+    val onDelete: ForeignKeyAction,
+    val onUpdate: ForeignKeyAction
+)
+
+/**
+ * https://www.sqlite.org/foreignkeys.html#fk_actions
+ */
+@Serializable(with = ForeignKeyActionSerializer::class)
+enum class ForeignKeyAction(val key: String) {
+    NO_ACTION("NO ACTION"),
+    RESTRICT("RESTRICT"),
+    SET_NULL("SET NULL"),
+    SET_DEFAULT("SET DEFAULT"),
+    CASCADE("CASCADE")
+}
diff --git a/src/main/kotlin/com/zynger/floorplan/model/serializer/ForeignKeyActionSerializer.kt b/src/main/kotlin/com/zynger/floorplan/model/serializer/ForeignKeyActionSerializer.kt
new file mode 100644
index 0000000..c38a14a
--- /dev/null
+++ b/src/main/kotlin/com/zynger/floorplan/model/serializer/ForeignKeyActionSerializer.kt
@@ -0,0 +1,18 @@
+package com.zynger.floorplan.model.serializer
+
+import com.zynger.floorplan.model.ForeignKeyAction
+import kotlinx.serialization.*
+
+@Serializer(forClass = ForeignKeyAction::class)
+object ForeignKeyActionSerializer {
+
+    override val descriptor: SerialDescriptor
+        get() = PrimitiveDescriptor("foreignKeyActionSerializer", PrimitiveKind.STRING)
+
+    override fun deserialize(decoder: Decoder): ForeignKeyAction {
+        val value = decoder.decodeString().toUpperCase()
+        return ForeignKeyAction.values().find { it.key == value }!!
+    }
+
+    override fun serialize(encoder: Encoder, value: ForeignKeyAction) = encoder.encodeString(value.key)
+}


Test patch:
diff --git a/src/test/kotlin/com/zynger/floorplan/dbml/ReferenceTest.kt b/src/test/kotlin/com/zynger/floorplan/dbml/ReferenceTest.kt
index a9efcad..bfdc7cc 100644
--- a/src/test/kotlin/com/zynger/floorplan/dbml/ReferenceTest.kt
+++ b/src/test/kotlin/com/zynger/floorplan/dbml/ReferenceTest.kt
@@ -1,6 +1,7 @@
 package com.zynger.floorplan.dbml
 
 import com.zynger.floorplan.model.ForeignKey
+import com.zynger.floorplan.model.ForeignKeyAction
 import org.junit.Assert
 import org.junit.Test
 
@@ -11,9 +12,8 @@ class ReferenceTest {
         private const val TO_TABLE = "referenced"
         private const val FROM_COLUMN = "userId"
         private const val TO_COLUMN = "id"
-        private const val GARBLE = "egal"
-        private const val ON_DELETE = GARBLE
-        private const val ON_UPDATE = GARBLE
+        private val ON_DELETE = ForeignKeyAction.NO_ACTION
+        private val ON_UPDATE = ForeignKeyAction.NO_ACTION
     }
 
     @Test
@@ -24,7 +24,7 @@ class ReferenceTest {
 
         Assert.assertEquals(
             """
-                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN
+                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN [delete: no action, update: no action]
             """.trimIndent(),
             reference.toString()
         )
@@ -38,7 +38,63 @@ class ReferenceTest {
 
         Assert.assertEquals(
             """
-                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN
+                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN [delete: no action, update: no action]
+            """.trimIndent(),
+            reference.toString()
+        )
+    }
+
+    @Test
+    fun `foreign key action gets translated for RESTRICT action`() {
+        val reference = Reference(FROM_TABLE,
+            ForeignKey(TO_TABLE, listOf(FROM_COLUMN), listOf(TO_COLUMN), ForeignKeyAction.RESTRICT, ForeignKeyAction.RESTRICT)
+        )
+
+        Assert.assertEquals(
+            """
+                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN [delete: restrict, update: restrict]
+            """.trimIndent(),
+            reference.toString()
+        )
+    }
+
+    @Test
+    fun `foreign key action gets translated for SET NULL action`() {
+        val reference = Reference(FROM_TABLE,
+            ForeignKey(TO_TABLE, listOf(FROM_COLUMN), listOf(TO_COLUMN), ForeignKeyAction.SET_NULL, ForeignKeyAction.SET_NULL)
+        )
+
+        Assert.assertEquals(
+            """
+                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN [delete: set null, update: set null]
+            """.trimIndent(),
+            reference.toString()
+        )
+    }
+
+    @Test
+    fun `foreign key action gets translated for SET DEFAULT action`() {
+        val reference = Reference(FROM_TABLE,
+            ForeignKey(TO_TABLE, listOf(FROM_COLUMN), listOf(TO_COLUMN), ForeignKeyAction.SET_DEFAULT, ForeignKeyAction.SET_DEFAULT)
+        )
+
+        Assert.assertEquals(
+            """
+                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN [delete: set default, update: set default]
+            """.trimIndent(),
+            reference.toString()
+        )
+    }
+
+    @Test
+    fun `foreign key action gets translated for CASCADE action`() {
+        val reference = Reference(FROM_TABLE,
+            ForeignKey(TO_TABLE, listOf(FROM_COLUMN), listOf(TO_COLUMN), ForeignKeyAction.CASCADE, ForeignKeyAction.CASCADE)
+        )
+
+        Assert.assertEquals(
+            """
+                Ref: $FROM_TABLE.$FROM_COLUMN - $TO_TABLE.$TO_COLUMN [delete: cascade, update: cascade]
             """.trimIndent(),
             reference.toString()
         )
diff --git a/src/test/kotlin/com/zynger/floorplan/dbml/TableTest.kt b/src/test/kotlin/com/zynger/floorplan/dbml/TableTest.kt
index a3b8d06..a8d1548 100644
--- a/src/test/kotlin/com/zynger/floorplan/dbml/TableTest.kt
+++ b/src/test/kotlin/com/zynger/floorplan/dbml/TableTest.kt
@@ -19,9 +19,8 @@ class TableTest {
         private const val INDEX_1_NAME = "index1"
         private const val INDEX_2_NAME = "index2"
 
-        private const val GARBLE = "egal"
-        private const val ON_DELETE = GARBLE
-        private const val ON_UPDATE = GARBLE
+        private val ON_DELETE = ForeignKeyAction.NO_ACTION
+        private val ON_UPDATE = ForeignKeyAction.NO_ACTION
     }
 
     @Test
@@ -153,7 +152,7 @@ class TableTest {
                   $COLUMN_NAME_1 varchar [note: 'not null']
                 }
                 
-                Ref: $TABLE_NAME.$COLUMN_NAME_1 - $TABLE_NAME_2.$COLUMN_NAME_3
+                Ref: $TABLE_NAME.$COLUMN_NAME_1 - $TABLE_NAME_2.$COLUMN_NAME_3 [delete: no action, update: no action]
             """.trimIndent(),
             table.toString()
         )
@@ -181,8 +180,8 @@ class TableTest {
                   userId varchar [note: 'not null']
                 }
                 
-                Ref: Users.username - Songs.userId
-                Ref: Users.userId - Albums.age
+                Ref: Users.username - Songs.userId [delete: no action, update: no action]
+                Ref: Users.userId - Albums.age [delete: no action, update: no action]
             """.trimIndent(),
             table.toString()
         )
@@ -220,8 +219,8 @@ class TableTest {
                   }
                 }
                 
-                Ref: Users.username - Songs.userId
-                Ref: Users.userId - Albums.age
+                Ref: Users.username - Songs.userId [delete: no action, update: no action]
+                Ref: Users.userId - Albums.age [delete: no action, update: no action]
             """.trimIndent(),
             table.toString()
         )