Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix binary decoders #72

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ project/plugins/project/
# Scala-IDE specific
.scala_dependencies
.worksheet

# IntelliJ
.idea
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ lazy val types = project
)
)
.dependsOn(core)
.dependsOn(core % "it->it")

lazy val benchmark = project
.settings(
description := "roc-benchmark",
moduleName := "roc-benchmark"
)
.configs( IntegrationTest )
.settings(allSettings:_*)
.settings(noPublishSettings)
.enablePlugins(JmhPlugin)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package roc.types.integrations

import java.time._
import java.time.temporal.{ChronoField, JulianFields}

import org.scalacheck._
import org.scalacheck.Prop.forAll
import org.scalacheck.Arbitrary.arbitrary
import org.specs2._
import roc.integrations.Client
import roc.postgresql.{ElementDecoder, Request, Text}
import com.twitter.util.Await
import roc.types._
import roc.types.decoders._
import jawn.ast.{JNull, JParser, JValue}

class BinaryDecodersIntegrationSpec extends Specification with Client with ScalaCheck {

//need a more sensible BigDecimal generator, because ScalaCheck goes crazy with it and we can't even stringify them
//this will be sufficient to test the decoder
implicit val arbBD: Arbitrary[BigDecimal] = Arbitrary(for {
precision <- Gen.choose(1, 32)
scale <- Gen.choose(-32, 32)
digits <- Gen.listOfN[Char](precision, Gen.numChar)
} yield BigDecimal(BigDecimal(digits.mkString).bigDecimal.movePointLeft(scale)))

implicit val arbCirce: Arbitrary[_root_.io.circe.Json] = JsonGenerators.arbJson
//arbitrary Json for Jawn
implicit val arbJson = Arbitrary[Json](Gen.resize(4, for {
circe <- arbitrary[_root_.io.circe.Json]
} yield JParser.parseFromString(circe.noSpaces).toOption.getOrElse(JNull)))

implicit val arbDate = Arbitrary[Date](for {
julian <- Gen.choose(1721060, 5373484) //Postgres date parser doesn't like dates outside year range 0000-9999
} yield LocalDate.now().`with`(JulianFields.JULIAN_DAY, julian))

implicit val arbTime = Arbitrary[Time](for {
usec <- Gen.choose(0L, 24L * 60 * 60 * 1000000 - 1)
} yield LocalTime.ofNanoOfDay(usec * 1000))

implicit val arbTimestampTz = Arbitrary[TimestampWithTZ](for {
milli <- Gen.posNum[Long]
} yield ZonedDateTime.ofInstant(Instant.ofEpochMilli(milli), ZoneId.systemDefault()))

def is = sequential ^ s2"""
Decoders
must decode string from binary $testString
must decode short from binary $testShort
must decode int from binary $testInt
must decode long from binary $testLong
must decode float from binary $testFloat
must decode double from binary $testDouble
must decode numeric from binary $testNumeric
must decode boolean from binary $testBoolean
must decode json from binary $testJson
must decode jsonb from binary $testJsonb
must decode date from binary $testDate
must decode time from binary $testTime
must decode timestamptz from binary $testTimestampTz
"""

def test[T : Arbitrary : ElementDecoder](
send: String,
typ: String,
toStr: T => String = (t: T) => t.toString,
tester: (T, T) => Boolean = (a: T, b: T) => a == b) = forAll {
(t: T) =>
//TODO: change this once prepared statements are available
val escaped = toStr(t).replaceAllLiterally("'", "\\'")
val query = s"SELECT $send('$escaped'::$typ) AS out"
val result = Await.result(Postgres.query(Request(query)))
val Text(name, oid, string) = result.head.get('out)
val bytes = string.stripPrefix("\\x").sliding(2,2).toArray.map(Integer.parseInt(_, 16)).map(_.toByte)
val out = implicitly[ElementDecoder[T]].binaryDecoder(bytes)
tester(t, out)
}

def testString = test[String]("textsend", "text")
def testShort = test[Short]("int2send", "int2")
def testInt = test[Int]("int4send", "int4")
def testLong = test[Long]("int8send", "int8")
def testFloat = test[Float]("float4send", "float4")
def testDouble = test[Double]("float8send", "float8")
def testNumeric = test[BigDecimal]("numeric_send", "numeric", _.bigDecimal.toPlainString)
def testBoolean = test[Boolean]("boolsend", "boolean")
def testJson = test[Json]("json_send", "json")
def testJsonb = test[Json]("jsonb_send", "jsonb")
def testDate = test[Date]("date_send", "date")
def testTime = test[Time]("time_send", "time")
def testTimestampTz = test[TimestampWithTZ](
"timestamptz_send",
"timestamptz",
ts => java.sql.Timestamp.from(ts.toInstant).toString,
(a, b) => a.getLong(ChronoField.MICRO_OF_DAY) == b.getLong(ChronoField.MICRO_OF_DAY) //postgres only keeps microsecond precision
)

//disabled - test server might not have hstore extension
def testHstore = test[HStore]("hstore_send", "hstore", { strMap =>
strMap.map {
case (k, v) =>
s""""${k.replaceAllLiterally("\"", "\\\"")}"=>"${v.getOrElse("NULL").replaceAllLiterally("\"", "\\\"")}""""
}.mkString(",")
})
}
39 changes: 39 additions & 0 deletions types/src/it/scala/roc/types/integrations/JsonGenerators.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package roc.types.integrations

import io.circe.{Json, JsonObject}
import io.circe.syntax._
import org.scalacheck.Arbitrary._
import org.scalacheck.{Arbitrary, Gen}

object JsonGenerators {

val genJsonScalarValue : Gen[Json] = Gen.oneOf(
arbitrary[Long] map (_.asJson),
Gen.choose(Int.MinValue.toDouble, Int.MaxValue.toDouble) map (_.asJson), //avoid parsing craziness
Gen.alphaStr map Json.fromString,
arbitrary[Boolean] map Json.fromBoolean
)

def genJsonArray(genValue: Gen[Json]) = Gen.listOf(genValue).map(Json.arr(_:_*))

def genJsonObject(genValue: Gen[Json]) = Gen.nonEmptyMap(for {
keySize <- Gen.choose(10, 20)
key <- Gen.resize(keySize, Gen.nonEmptyListOf(Gen.alphaNumChar).map(_.mkString))
value <- genValue
} yield (key, value)) map JsonObject.fromMap map Json.fromJsonObject

def genJson(level: Int) : Gen[Json] = if(level == 0)
genJsonScalarValue
else Gen.oneOf(
genJsonScalarValue,
genJsonArray(genJson(level - 1)),
genJsonObject(genJson(level - 1)))

implicit val arbJson : Arbitrary[Json] = Arbitrary(for {
size <- Gen.size
level <- Gen.choose(0, size)
json <- genJson(level)
} yield json)


}
Loading