-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from tminglei/pgjson
Pgjson
- Loading branch information
Showing
8 changed files
with
244 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
src/main/scala/com/github/tminglei.slickpg/PgJsonSupport.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package com.github.tminglei.slickpg | ||
|
||
import scala.slick.driver.{BasicProfile, PostgresDriver} | ||
import scala.slick.ast.Library.{SqlFunction, SqlOperator} | ||
import scala.slick.lifted._ | ||
import scala.slick.session.{PositionedResult, PositionedParameters} | ||
import org.postgresql.util.PGobject | ||
import scala.slick.ast.Node | ||
import org.json4s._ | ||
|
||
trait PgJsonSupport[T] { driver: PostgresDriver => | ||
|
||
val jsonMethods: JsonMethods[T] | ||
|
||
trait JsonImplicits { | ||
implicit val jsonTypeMapper = new JsonTypeMapper(jsonMethods) | ||
|
||
implicit def jsonColumnExtensionMethods(c: Column[JValue])( | ||
implicit tm: TypeMapper[JValue], tm1: TypeMapper[List[String]]) = { | ||
new JsonColumnExtensionMethods[JValue](c) | ||
} | ||
implicit def jsonOptionColumnExtensionMethods(c: Column[Option[JValue]])( | ||
implicit tm: TypeMapper[JValue], tm1: TypeMapper[List[String]]) = { | ||
new JsonColumnExtensionMethods[Option[JValue]](c) | ||
} | ||
} | ||
|
||
///////////////////////////////////////////////////////////////// | ||
|
||
object JsonLibrary { | ||
val -> = new SqlOperator("->") | ||
val ->> = new SqlOperator("->>") | ||
val #> = new SqlOperator("#>") | ||
val #>> = new SqlOperator("#>>") | ||
|
||
val arrayLength = new SqlFunction("json_array_length") | ||
val arrayElements = new SqlFunction("json_array_elements") | ||
val objectKeys = new SqlFunction("json_object_keys") | ||
// val rowToJson = new SqlFunction("row_to_json") //not support, since "row" type not supported by slick/slick-pg yet | ||
// val jsonEach = new SqlFunction("json_each") //not support, since "row" type not supported by slick/slick-pg yet | ||
// val jsonEachText = new SqlFunction("json_each_text") //not support, since "row" type not supported by slick/slick-pg yet | ||
// val jsonPopulateRecord = new SqlFunction("json_populate_record") //not support, since "row" type not supported by slick/slick-pg yet | ||
// val jsonPopulateRecordset = new SqlFunction("json_populate_recordset") //not support, since "row" type not supported by slick/slick-pg yet | ||
} | ||
|
||
class JsonColumnExtensionMethods[P1](val c: Column[P1])( | ||
implicit tm: TypeMapper[JValue], tm1: TypeMapper[List[String]]) | ||
extends ExtensionMethods[JValue, P1] { | ||
/** Note: json array's index starts with 0 */ | ||
def ~> [P2, R](index: Column[P2])(implicit om: o#arg[Int, P2]#to[JValue, R]) = { | ||
om(JsonLibrary.->.column[JValue](n, Node(index))) | ||
} | ||
def ~>>[P2, R](index: Column[P2])(implicit om: o#arg[Int, P2]#to[String, R]) = { | ||
om(JsonLibrary.->>.column[String](n, Node(index))) | ||
} | ||
def +> [P2, R](key: Column[P2])(implicit om: o#arg[String, P2]#to[JValue, R]) = { | ||
om(JsonLibrary.->.column[JValue](n, Node(key))) | ||
} | ||
def +>>[P2, R](key: Column[P2])(implicit om: o#arg[String, P2]#to[String, R]) = { | ||
om(JsonLibrary.->>.column[String](n, Node(key))) | ||
} | ||
def #> [P2, R](keyPath: Column[P2])(implicit om: o#arg[List[String], P2]#to[JValue, R]) = { | ||
om(JsonLibrary.#>.column[JValue](n, Node(keyPath))) | ||
} | ||
def #>>[P2, R](keyPath: Column[P2])(implicit om: o#arg[List[String], P2]#to[String, R]) = { | ||
om(JsonLibrary.#>>.column[String](n, Node(keyPath))) | ||
} | ||
|
||
def arrayLength[R](implicit om: o#to[Int, R]) = om(JsonLibrary.arrayLength.column(n)) | ||
def arrayElements[R](implicit om: o#to[JValue, R]) = om(JsonLibrary.arrayElements.column(n)) | ||
def objectKeys[R](implicit om: o#to[String, R]) = om(JsonLibrary.objectKeys.column(n)) | ||
} | ||
|
||
////////////////////////////////////////////////////////////////// | ||
|
||
class JsonTypeMapper(jsonMethods: JsonMethods[T]) extends TypeMapperDelegate[JValue] with BaseTypeMapper[JValue] { | ||
import jsonMethods._ | ||
|
||
def apply(v1: BasicProfile): TypeMapperDelegate[JValue] = this | ||
|
||
//---------------------------------------------------------- | ||
def zero = null | ||
|
||
def sqlType = java.sql.Types.OTHER | ||
|
||
def sqlTypeName = "json" | ||
|
||
def setValue(v: JValue, p: PositionedParameters) = p.setObject(mkPgObject(v), sqlType) | ||
|
||
def setOption(v: Option[JValue], p: PositionedParameters) = p.setObjectOption(v.map(mkPgObject), sqlType) | ||
|
||
def nextValue(r: PositionedResult) = r.nextStringOption().map(parse(_)).getOrElse(zero) | ||
|
||
def updateValue(v: JValue, r: PositionedResult) = r.updateObject(mkPgObject(v)) | ||
|
||
override def valueToSQLLiteral(v: JValue) = pretty(render(v)) | ||
|
||
/// | ||
private def mkPgObject(v: JValue) = { | ||
val obj = new PGobject | ||
obj.setType(sqlTypeName) | ||
obj.setValue(compact(render(v))) | ||
obj | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
src/test/scala/com/github/tminglei.slickpg/PgJsonSupportTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.github.tminglei.slickpg | ||
|
||
import org.junit._ | ||
import org.junit.Assert._ | ||
import org.json4s._ | ||
|
||
class PgJsonSupportTest { | ||
import MyPostgresDriver.simple._ | ||
import MyPostgresDriver.jsonMethods._ | ||
|
||
val db = Database.forURL(url = "jdbc:postgresql://localhost/test?user=test", driver = "org.postgresql.Driver") | ||
|
||
case class JsonBean(id: Long, json: JValue) | ||
|
||
object JsonTestTable extends Table[JsonBean](Some("test"), "JsonTest") { | ||
def id = column[Long]("id", O.AutoInc, O.PrimaryKey) | ||
def json = column[JValue]("json") | ||
|
||
def * = id ~ json <> (JsonBean, JsonBean unapply _) | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
val testRec1 = JsonBean(33L, parse(""" { "a":101, "b":"aaa", "c":[3,4,5,9] } """)) | ||
val testRec2 = JsonBean(35L, parse(""" [ {"a":"v1","b":2}, {"a":"v5","b":3} ] """)) | ||
|
||
@Test | ||
def testJsonFunctions(): Unit = { | ||
db withSession { implicit session: Session => | ||
JsonTestTable.insert(testRec1) | ||
JsonTestTable.insert(testRec2) | ||
|
||
val json1 = parse(""" {"a":"v1","b":2} """) | ||
val json2 = parse(""" {"a":"v5","b":3} """) | ||
|
||
val q0 = JsonTestTable.where(_.id === testRec2.id.bind).map(_.json) | ||
println(s"sql0 = ${q0.selectStatement}") | ||
assertEquals(JArray(List(json1,json2)), q0.first()) | ||
|
||
// pretty(render(JInt(101))) will get "101", but parse("101") will fail, since json string must start with '{' or '[' | ||
// val q1 = JsonTestTable.where(_.id === testRec1.id.bind).map(_.json.+>("a")) | ||
// println(s"'+>' sql = ${q1.selectStatement}") | ||
// assertEquals(JInt(101), q1.first()) | ||
|
||
val q11 = JsonTestTable.where(_.json.+>>("a") === "101".bind).map(_.json.+>>("c")) | ||
println(s"'+>>' sql = ${q11.selectStatement}") | ||
assertEquals("[3,4,5,9]", q11.first()) | ||
|
||
val q12 = JsonTestTable.where(_.json.+>>("a") === "101".bind).map(_.json.+>("c")) | ||
println(s"'+>' sql = ${q12.selectStatement}") | ||
assertEquals(JArray(List(JInt(3), JInt(4), JInt(5), JInt(9))), q12.first()) | ||
|
||
// json array's index starts with 0 | ||
val q2 = JsonTestTable.where(_.id === testRec2.id).map(_.json.~>(1)) | ||
println(s"'~>' sql = ${q2.selectStatement}") | ||
assertEquals(json2, q2.first()) | ||
|
||
val q21 = JsonTestTable.where(_.id === testRec2.id).map(_.json.~>>(1)) | ||
println(s"'~>>' sql = ${q21.selectStatement}") | ||
assertEquals("""{"a":"v5","b":3}""", q21.first()) | ||
|
||
val q3 = JsonTestTable.where(_.id === testRec2.id).map(_.json.arrayLength) | ||
println(s"'arrayLength' sql = ${q3.selectStatement}") | ||
assertEquals(2, q3.first()) | ||
|
||
val q4 = JsonTestTable.where(_.id === testRec2.id).map(_.json.arrayElements) | ||
println(s"'arrayElements' sql = ${q4.selectStatement}") | ||
assertEquals(List(json1, json2), q4.list()) | ||
|
||
val q41 = JsonTestTable.where(_.id === testRec2.id).map(_.json.arrayElements) | ||
println(s"'arrayElements' sql = ${q41.selectStatement}") | ||
assertEquals(json1, q41.first()) | ||
|
||
val q5 = JsonTestTable.where(_.id === testRec1.id).map(_.json.objectKeys) | ||
println(s"'objectKeys' sql = ${q5.selectStatement}") | ||
assertEquals(List("a","b","c"), q5.list()) | ||
|
||
val q51 = JsonTestTable.where(_.id === testRec1.id).map(_.json.objectKeys) | ||
println(s"'objectKeys' sql = ${q51.selectStatement}") | ||
assertEquals("a", q51.first()) | ||
} | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
@Before | ||
def createTables(): Unit = { | ||
db withSession { implicit session: Session => | ||
JsonTestTable.ddl create | ||
} | ||
} | ||
|
||
@After | ||
def dropTables(): Unit = { | ||
db withSession { implicit session: Session => | ||
JsonTestTable.ddl drop | ||
} | ||
} | ||
} |