All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.crobox.clickhouse.dsl.marshalling.ClickhouseJsonSupport.scala Maven / Gradle / Ivy

package com.crobox.clickhouse.dsl.marshalling

import com.crobox.clickhouse.time.IntervalStart
import org.joda.time.format.{DateTimeFormatter, DateTimeFormatterBuilder, ISODateTimeFormat}
import org.joda.time.{DateTime, DateTimeZone}
import spray.json.{JsNumber, JsString, JsValue, JsonFormat, deserializationError, _}

import scala.util.Try
import scala.util.matching.Regex

trait ClickhouseJsonSupport {

  /**
   * Adds support for org.joda.time.DateTime format specific to clickhouse
   */
  implicit object ClickhouseIntervalStartFormat extends JsonFormat[IntervalStart] {

    override def write(obj: IntervalStart): JsValue = JsNumber(obj.getMillis)

    val month: Regex                 = """(\d+)_(.*)""".r
    val date: Regex                  = """(.+)_(.*)""".r
    val nanoTimestamp: Regex         = """^(\d{16})$""".r
    val msTimestamp: Regex           = """^(\d{13})$""".r
    val timestamp: Regex             = """^(\d{10})$""".r
    val RelativeMonthsSinceUnixStart = 23641

    val UnixStartTimeWithoutTimeZone            = "1970-01-01T00:00:00.000"
    val formatter: DateTimeFormatter            = ISODateTimeFormat.date()
    private val isoFormatter: DateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis

    val readFormatter: DateTimeFormatter = new DateTimeFormatterBuilder()
      .append(isoFormatter.getPrinter,
              Array(isoFormatter.getParser, ISODateTimeFormat.date().withZone(DateTimeZone.UTC).getParser))
      .toFormatter
      .withOffsetParsed()

    /*
     * It read the dates back as UTC, but it does contain the corresponding milliseconds relative to the timezone used in the initial request
     *
     * */
    override def read(json: JsValue): IntervalStart =
      json match {
        case JsString(value) =>
          value match {
            case month(relativeMonth, timezoneId) =>
              new DateTime(UnixStartTimeWithoutTimeZone)
                .withZoneRetainFields(DateTimeZone.forID(timezoneId))
                .plusMonths(relativeMonth.toInt - RelativeMonthsSinceUnixStart)
                .withZone(DateTimeZone.UTC)
            case date(dateOnly, timezoneId) =>
              //should handle quarter and year grouping as it returns a date
              formatter
                .parseDateTime(dateOnly)
                .withZoneRetainFields(DateTimeZone.forID(timezoneId))
                .withZone(DateTimeZone.UTC)
            case nanoTimestamp(millis) => new DateTime(millis.toLong / 1000, DateTimeZone.UTC)
            case msTimestamp(millis)   => new DateTime(millis.toLong, DateTimeZone.UTC)
            case timestamp(secs)       => new DateTime(secs.toLong * 1000, DateTimeZone.UTC)
            case _                     =>
              // sometimes clickhouse mistakenly returns a long / int value as JsString. Therefor, first try to
              // parse it as a long...
              val dateTime = Try {
                new DateTime(value.toLong, DateTimeZone.UTC)
              }.toOption

              // continue with parsing using the formatter
              dateTime.getOrElse {
                try {
                  formatter.parseDateTime(value)
                } catch {
                  case _: IllegalArgumentException => error(s"Couldn't parse $value into valid date time")
                  case _: UnsupportedOperationException =>
                    error("Unsupported operation, programmatic misconfiguration?")
                }
              }
          }
        case JsNumber(millis) => new DateTime(millis.longValue, DateTimeZone.UTC)
        case _                => throw DeserializationException(s"Unknown date format read from clickhouse for $json")
      }

    def error(v: Any): DateTime = {
      val example = readFormatter.print(0)
      deserializationError(
        f"'$v' is not a valid date value. Dates must be in compact ISO-8601 format, e.g. '$example'"
      )
    }
  }

}
object ClickhouseJsonSupport extends DefaultJsonProtocol with ClickhouseJsonSupport




© 2015 - 2024 Weber Informatics LLC | Privacy Policy