diff --git a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/models/ColumnType.scala b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/models/ColumnType.scala new file mode 100644 index 0000000..5ef9e7e --- /dev/null +++ b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/models/ColumnType.scala @@ -0,0 +1,37 @@ +package net.wiringbits.spra.admin.models + +sealed trait ColumnType { + val value: String +} + +object ColumnType { + case class Date(value: String) extends ColumnType + case class Text(value: String) extends ColumnType + case class Bytea(value: String) extends ColumnType + case class Int(value: String) extends ColumnType + case class Decimal(value: String) extends ColumnType + case class UUID(value: String) extends ColumnType + + def parseColumnType(columnType: String): ColumnType = { + // 'contains' is used because PostgreSQL types may include additional details like precision or scale + // https://www.postgresql.org/docs/8.1/datatype.html + val isInt = List("int", "serial").exists(columnType.contains) + val isDecimal = List("float", "decimal").exists(columnType.contains) + val isBytea = columnType == "bytea" + val isUUID = columnType == "uuid" + val isDate = List("date", "time").exists(columnType.contains) + + if (isDate) + Date(columnType) + else if (isDecimal) + Decimal(columnType) + else if (isBytea) + Bytea(columnType) + else if (isInt) + Int(columnType) + else if (isUUID) + UUID(columnType) + else + Text(columnType) + } +} diff --git a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/DatabaseTablesDAO.scala b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/DatabaseTablesDAO.scala index 2aed493..d539fbd 100644 --- a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/DatabaseTablesDAO.scala +++ b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/DatabaseTablesDAO.scala @@ -2,6 +2,7 @@ package net.wiringbits.spra.admin.repositories.daos import anorm.{SqlParser, SqlStringInterpolation} import net.wiringbits.spra.admin.config.{CustomDataType, PrimaryKeyDataType, TableSettings} +import net.wiringbits.spra.admin.models.ColumnType import net.wiringbits.spra.admin.repositories.models.* import net.wiringbits.spra.admin.utils.models.{FilterParameter, QueryParameters} import net.wiringbits.spra.admin.utils.{QueryBuilder, StringRegex} @@ -38,7 +39,7 @@ object DatabaseTablesDAO { val fields = for { columnNumber <- 1 to numberOfColumns columnName = metadata.getColumnName(columnNumber) - columnType = metadata.getColumnTypeName(columnNumber) + columnType = ColumnType.parseColumnType(metadata.getColumnTypeName(columnNumber)) } yield TableColumn(columnName, columnType) fields.toList } finally { diff --git a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/package.scala b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/package.scala index 691e958..6a57807 100644 --- a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/package.scala +++ b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/daos/package.scala @@ -1,6 +1,8 @@ package net.wiringbits.spra.admin.repositories import anorm.* +import anorm.SqlParser.get +import net.wiringbits.spra.admin.models.ColumnType import net.wiringbits.spra.admin.repositories.models.{DatabaseTable, ForeignKey, TableColumn} package object daos { @@ -28,10 +30,13 @@ package object daos { } val tableColumnParser: RowParser[TableColumn] = { - Macro.parser[TableColumn]( - "column_name", - "data_type" - ) + get[String]("column_name") ~ + get[String]("data_type") map { case name ~ columnTypeStr => + TableColumn( + name = name, + `type` = ColumnType.parseColumnType(columnTypeStr) + ) + } } val foreignKeyParser: RowParser[ForeignKey] = { diff --git a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/models/TableColumn.scala b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/models/TableColumn.scala index 437a0c4..a813694 100644 --- a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/models/TableColumn.scala +++ b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/repositories/models/TableColumn.scala @@ -1,6 +1,8 @@ package net.wiringbits.spra.admin.repositories.models +import net.wiringbits.spra.admin.models.ColumnType + case class TableColumn( name: String, - `type`: String + `type`: ColumnType ) diff --git a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/services/AdminService.scala b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/services/AdminService.scala index c7b3f21..c489a3a 100644 --- a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/services/AdminService.scala +++ b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/services/AdminService.scala @@ -66,7 +66,7 @@ class AdminService @Inject() ( None AdminGetTables.Response.TableColumn( name = fieldName, - `type` = column.`type`, + `type` = column.`type`.value, editable = isEditable, reference = reference, filterable = isFilterable, diff --git a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/utils/QueryBuilder.scala b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/utils/QueryBuilder.scala index 8193eba..90756a8 100644 --- a/spra-play-server/src/main/scala/net/wiringbits/spra/admin/utils/QueryBuilder.scala +++ b/spra-play-server/src/main/scala/net/wiringbits/spra/admin/utils/QueryBuilder.scala @@ -20,7 +20,7 @@ object QueryBuilder { } for ((tableColumn, _) <- fieldsAndValues) { sqlFields.append(s", ${tableColumn.name}") - sqlValues.append(s", ?::${tableColumn.`type`}") + sqlValues.append(s", ?::${tableColumn.`type`.value}") } s""" @@ -36,7 +36,7 @@ object QueryBuilder { def update(tableName: String, body: Map[TableColumn, String], primaryKeyField: String): String = { val updateStatement = new mutable.StringBuilder("SET") for ((tableField, value) <- body) { - val resultStatement = if (value == "null") "NULL" else s"?::${tableField.`type`}" + val resultStatement = if (value == "null") "NULL" else s"?::${tableField.`type`.value}" val statement = s" ${tableField.name} = $resultStatement," updateStatement.append(statement) } diff --git a/spra-play-server/src/test/scala/net/wiringbits/spra/admin/QueryBuilderSpec.scala b/spra-play-server/src/test/scala/net/wiringbits/spra/admin/QueryBuilderSpec.scala index 7f53bfc..66f12c0 100644 --- a/spra-play-server/src/test/scala/net/wiringbits/spra/admin/QueryBuilderSpec.scala +++ b/spra-play-server/src/test/scala/net/wiringbits/spra/admin/QueryBuilderSpec.scala @@ -1,5 +1,6 @@ package net.wiringbits.spra.admin +import net.wiringbits.spra.admin.models.ColumnType import net.wiringbits.spra.admin.repositories.models.TableColumn import net.wiringbits.spra.admin.utils.QueryBuilder import org.scalatest.matchers.must.Matchers.{be, must} @@ -20,7 +21,10 @@ class QueryBuilderSpec extends AnyWordSpec { |""".stripMargin val tableName = "users" val body = - Map(TableColumn("email", "citext") -> "wiringbits@wiringbits.net", TableColumn("name", "text") -> "wiringbits") + Map( + TableColumn("email", ColumnType.parseColumnType("citext")) -> "wiringbits@wiringbits.net", + TableColumn("name", ColumnType.parseColumnType("text")) -> "wiringbits" + ) val primaryKeyField = "user_id" val response = QueryBuilder.create(tableName, body, primaryKeyField) @@ -58,8 +62,8 @@ class QueryBuilderSpec extends AnyWordSpec { |""".stripMargin val tableName = "users" val body = Map( - TableColumn("email", "citext") -> "wiringbits@wiringbits.net", - TableColumn("name", "text") -> "wiringbits@wiringbits.net" + TableColumn("email", ColumnType.parseColumnType("citext")) -> "wiringbits@wiringbits.net", + TableColumn("name", ColumnType.parseColumnType("text")) -> "wiringbits@wiringbits.net" ) val primaryKeyField = "user_id" @@ -76,9 +80,9 @@ class QueryBuilderSpec extends AnyWordSpec { |""".stripMargin val tableName = "users" val body = Map( - TableColumn("email", "citext") -> "wiringbits@wiringbits.net", - TableColumn("name", "text") -> "wiringbits@wiringbits.net", - TableColumn("phone_number", "text") -> "null" + TableColumn("email", ColumnType.parseColumnType("citext")) -> "wiringbits@wiringbits.net", + TableColumn("name", ColumnType.parseColumnType("text")) -> "wiringbits@wiringbits.net", + TableColumn("phone_number", ColumnType.parseColumnType("text")) -> "null" ) val primaryKeyField = "user_id"