Как вставить JSON в таблицу postgres с помощью Anorm? - PullRequest
0 голосов
/ 28 января 2020

Я получаю исключение времени выполнения при попытке вставить строку JSON в столбец JSON. Строка у меня выглядит как """{"Events": []}""", таблица имеет столбец, определенный как status JSONB NOT NULL. Я могу вставить строку в таблицу из командной строки без проблем. Я определил метод для вставки как:

    import play.api.libs.json._
    import anorm._
    import anorm.postgresql._

    def createStatus(
      status: String,
      created: LocalDateTime = LocalDateTime.now())(implicit c: SQLConnection): Unit = {
      SQL(s"""
             |INSERT INTO status_feed
             |  (status, created)
             |VALUES
             |  ({status}, {created})
             |""".stripMargin)
        .on(
          'status -> Json.parse("{}"), // n.b. would be Json.parse(status) but this provides a concise error message
          'created -> created)
        .execute()
    }

, и вызов его вызывает следующую ошибку:

TypeDoesNotMatch(Cannot convert {}: org.postgresql.util.PGobject to String for column ColumnName(status_feed.status,Some(status)))
anorm.AnormException: TypeDoesNotMatch(Cannot convert {}: org.postgresql.util.PGobject to String for column ColumnName(status_feed.status,Some(status)))

Я выполнил множество поисков этой проблемы, но есть ничего об этом конкретном c сценарии использования, который я смог найти, - большинство вытаскивает json столбцов в классы вариантов. Я пробовал немного разные форматы, используя JSValue в Spray- json, JsValue для Play, просто передавая строку как есть и приводя в запросе с ::JSONB, и все они выдают одинаковую ошибку.

Обновление: вот SQL, который создал таблицу:

  CREATE TABLE status_feed (
    id SERIAL PRIMARY KEY,
    status JSONB NOT NULL,
    created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
  )

Ответы [ 2 ]

1 голос
/ 29 января 2020

Ошибка не в значениях, заданных для .executeInsert, а в разборе результата INSERT (вставленный ключ).

import java.sql._

// postgres=# CREATE TABLE test(foo JSONB NOT NULL);

val jdbcUrl = "jdbc:postgresql://localhost:32769/postgres"
val props = new java.util.Properties()
props.setProperty("user", "postgres")
props.setProperty("password", "mysecretpassword")

implicit val con = DriverManager.getConnection(jdbcUrl, props)

import anorm._, postgresql._
import play.api.libs.json._

SQL"""INSERT INTO test(foo) VALUES(${Json.obj("foo" -> 1)})""".
  executeInsert(SqlParser.scalar[JsValue].singleOpt)

// Option[play.api.libs.json.JsValue] = Some({"foo":1})

/*
postgres=# SELECT * FROM test ;
    foo     
------------
 {"foo": 1}
 */

Кстати, интерполяция простой строки бесполезна .

0 голосов
/ 29 января 2020

Оказывается, cchantep был прав, это был парсер, который я использовал. Тестовая среда, которую я использую, проглотила трассировку стека, и я предположила, что проблема была в вставке, но на самом деле взорвалась следующая строка в тесте, где я использую синтаксический анализатор.

Класс case и анализатор были определяется как:

case class StatusFeed(
  status: String,
  created: LocalDateTime) {
  val ItemsStatus: Status = status.parseJson.convertTo[Status]
}

object StatusFeed extends DefaultJsonProtocol {
  val fields: String = sqlFields[StatusFeed]() // helper function that results in "created, status"
  // used in SQL as RETURNING ${StatusFeed.fields}
  val parser: RowParser[StatusFeed] =
    Macro.namedParser[StatusFeed](Macro.ColumnNaming.SnakeCase)
  // json formatter for Status
}

Как определено, анализатор пытается прочитать столбец JSONB из результирующего набора в строку status. Изменение fields на val fields: String = "created, status::TEXT" решает проблему, хотя приведение может быть дорогим. В качестве альтернативы, определение status как JsValue вместо String и предоставление неявного для anorm (адаптированного из этого ответа для использования spray- json) решает проблему:

  implicit def columnToJsValue: Column[JsValue] = anorm.Column.nonNull[JsValue] { (value, meta) =>
    val MetaDataItem(qualified, nullable, clazz) = meta
    value match {
      case json: org.postgresql.util.PGobject => Right(json.getValue.parseJson)
      case _ =>
        Left(TypeDoesNotMatch(
          s"Cannot convert $value: ${value.asInstanceOf[AnyRef].getClass} to Json for column $qualified"))
    }
  }
...