diff --git a/README.md b/README.md
index 5458cdfc..6ddaf5b6 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Install
-------
To use `slick-pg` in [sbt](http://www.scala-sbt.org/ "slick-sbt") project, add the following to your project file:
```scala
-libraryDependencies += "com.github.tminglei" % "slick-pg_2.10.1" % "0.1.3.1"
+libraryDependencies += "com.github.tminglei" % "slick-pg_2.10.1" % "0.1.5"
```
Or, in [maven](http://maven.apache.org/ "maven") project, you can add `slick-pg` to your `pom.xml` like this:
@@ -24,7 +24,7 @@ Or, in [maven](http://maven.apache.org/ "maven") project, you can add `slick-pg`
com.github.tminglei
slick-pg_2.10.1
- 0.1.3.1
+ 0.1.5
```
@@ -121,6 +121,7 @@ val db = Database.forURL(url = "jdbc:postgresql://localhost/test?user=test", dri
Data types/operators/functions
------------------------------
- [Array's operators/functions](https://github.com/tminglei/slick-pg/tree/master/src/main/scala/com/github/tminglei.slickpg#array "Array's operators/functions")
+- [JSON's operators/functions](https://github.com/tminglei/slick-pg/tree/master/src/main/scala/com/github/tminglei.slickpg#json "JSON's operators/functions")
- [Datetime's operators/functions](https://github.com/tminglei/slick-pg/tree/master/src/main/scala/com/github/tminglei.slickpg#datetime "Datetime's operators/functions")
- [Range's operators/functions](https://github.com/tminglei/slick-pg/tree/master/src/main/scala/com/github/tminglei.slickpg#range "Range's operators/functions")
- [HStore's operators/functions](https://github.com/tminglei/slick-pg/tree/master/src/main/scala/com/github/tminglei.slickpg#hstore "HStore's operators/functions")
@@ -130,6 +131,9 @@ Data types/operators/functions
Version history
---------------
+v0.1.5 (29-Sep-2013):
+1) support pg json
+
v0.1.3.1 (13-Sep-2013):
1) fix Issue #10
diff --git a/example/project/Build.scala b/example/project/Build.scala
index bcb8b79e..e509a636 100644
--- a/example/project/Build.scala
+++ b/example/project/Build.scala
@@ -19,9 +19,10 @@ object ExampleBuild extends Build {
libraryDependencies := Seq(
"com.typesafe.slick" % "slick_2.10" % "1.0.1",
+ "com.github.tminglei" % "slick-pg_2.10.1" % "0.1.3.1",
+ "org.postgresql" % "postgresql" % "9.2-1003-jdbc4",
"com.vividsolutions" % "jts" % "1.13",
- "postgresql" % "postgresql" % "9.1-901.jdbc4",
- "com.github.tminglei" % "slick-pg_2.10.1" % "0.1.1"
+ "org.json4s" % "json4s-native_2.10" % "3.2.5"
),
resolvers += Resolver.mavenLocal,
resolvers += Resolver.sonatypeRepo("snapshots"),
diff --git a/example/src/scala/com.example/MyPostgresDriver.scala b/example/src/scala/com.example/MyPostgresDriver.scala
index 85e3ccde..0b36933f 100644
--- a/example/src/scala/com.example/MyPostgresDriver.scala
+++ b/example/src/scala/com.example/MyPostgresDriver.scala
@@ -7,8 +7,12 @@ trait MyPostgresDriver extends PostgresDriver
with PgArraySupport
with PgRangeSupport
with PgHStoreSupport
+// with PgJsonSupport[text.Document]
with PgSearchSupport
- with PostGISSupport {
+ with PostGISSupport
+ with PgDatetimeSupport {
+
+// override val jsonMethods = org.json4s.native.JsonMethods
override val Implicit = new ImplicitsPlus {}
override val simple = new SimpleQLPlus {}
@@ -18,8 +22,10 @@ trait MyPostgresDriver extends PostgresDriver
with ArrayImplicits
with RangeImplicits
with HStoreImplicits
+// with JsonImplicits
with SearchImplicits
with PostGISImplicits
+ with DatetimeImplicits
trait SimpleQLPlus extends SimpleQL
with ImplicitsPlus
diff --git a/project/Build.scala b/project/Build.scala
index 9ed8bf2a..fd0347d0 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -6,7 +6,7 @@ object SlickPgBuild extends Build {
lazy val theSettings = Seq(
name := "slick-pg",
description := "Slick extensions for PostgreSQL",
- version := "0.1.3.1",
+ version := "0.1.5",
organizationName := "slick-pg",
organization := "com.github.tminglei",
@@ -18,8 +18,11 @@ object SlickPgBuild extends Build {
"-language:postfixOps"),
libraryDependencies := Seq(
"com.typesafe.slick" % "slick_2.10" % "1.0.0",
- "com.vividsolutions" % "jts" % "1.13",
"org.postgresql" % "postgresql" % "9.2-1003-jdbc4",
+ "com.vividsolutions" % "jts" % "1.13",
+ "org.json4s" % "json4s-ast_2.10" % "3.2.5",
+ "org.json4s" % "json4s-core_2.10" % "3.2.5",
+ "org.json4s" % "json4s-native_2.10" % "3.2.5" % "test",
"junit" % "junit" % "4.11" % "test",
"com.novocode" % "junit-interface" % "0.10" % "test"
),
diff --git a/src/main/scala/com/github/tminglei.slickpg/PgJsonSupport.scala b/src/main/scala/com/github/tminglei.slickpg/PgJsonSupport.scala
new file mode 100644
index 00000000..c0824779
--- /dev/null
+++ b/src/main/scala/com/github/tminglei.slickpg/PgJsonSupport.scala
@@ -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
+ }
+ }
+}
diff --git a/src/main/scala/com/github/tminglei.slickpg/README.md b/src/main/scala/com/github/tminglei.slickpg/README.md
index 7fe9874f..b1e431c0 100644
--- a/src/main/scala/com/github/tminglei.slickpg/README.md
+++ b/src/main/scala/com/github/tminglei.slickpg/README.md
@@ -13,10 +13,22 @@ Supported data type's operators/functions
| + | || | array-to-element concatenation| ARRAY[4,5,6] || 7 | {4,5,6,7} |
| +: | || | element-to-array concatenation| 3 || ARRAY[4,5,6] | {3,4,5,6} |
| length | array_length | length of the array/dimension | array_length(array[1,2,3], 1) | 3 |
-| unnest | unnest | expand array to a set of rows | unnest(ARRAY[1,2]) | 1
2
(2 rows) |
+| unnest | unnest | expand array to a set of rows | unnest(ARRAY[1,2]) | 1
2
(2 rows) |
+#### JSON
+| Slick Operator/Function | PG Operator/Function | Description | Example | Result |
+| ----------------------- | -------------------- | ----------------------------- | ------------------------------- | ------ |
+| ~> | -> | Get JSON array element | '[1,2,3]'::json->2 | 3 |
+| ~>> | ->> | Get JSON array element as text| '[1,2,3]'::json->>2 | "3" |
+| +> | -> | Get JSON object field | '{"a":1,"b":2}'::json->'b' | 2 |
+| +>> | ->> | Get JSON object field as text | '{"a":1,"b":2}'::json->>'b' | "2" |
+| #> | #> | Get JSON object at specified path | '{"a":[1,2,3],"b":[4,5,6]}'::json#>'{a,2}' | 3 |
+| #>> | #>> | Get JSON object at specified path as text | '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' | "3" |
+| arrayLength | json_array_length | Returns elem number of outermost JSON array | json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') | 5 |
+| arrayElements | json_array_elements | Expands JSON array to set of JSON elements | json_array_elements('[1,true, [2,false]]') | value
-------------
1
true
[2,false] |
+| objectKeys | json_object_keys | Returns set of keys in outermost JSON object | json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') | json_object_keys
----------------
f1
f2 |
-### Datetime
+#### Datetime
| Slick Operator/Function | PG Operator/Function | Description | Example | Result |
| ----------------------- | -------------------- | ----------------------- | ---------------------------------------------- | ------------------------ |
| +++ | + | timestamp + interval | timestamp '2001-09-28 01:00' + interval '23 hours'|timestamp '2001-09-29 00:00:00'|
diff --git a/src/test/scala/com/github/tminglei.slickpg/MyPostgresDriver.scala b/src/test/scala/com/github/tminglei.slickpg/MyPostgresDriver.scala
index 942c2bda..dfddc5d2 100644
--- a/src/test/scala/com/github/tminglei.slickpg/MyPostgresDriver.scala
+++ b/src/test/scala/com/github/tminglei.slickpg/MyPostgresDriver.scala
@@ -7,9 +7,12 @@ trait MyPostgresDriver extends PostgresDriver
with PgDatetimeSupport
with PgRangeSupport
with PgHStoreSupport
+ with PgJsonSupport[text.Document]
with PgSearchSupport
with PostGISSupport {
+ override val jsonMethods = org.json4s.native.JsonMethods
+
override val Implicit = new ImplicitsPlus {}
override val simple = new SimpleQLPlus {}
@@ -19,6 +22,7 @@ trait MyPostgresDriver extends PostgresDriver
with DatetimeImplicits
with RangeImplicits
with HStoreImplicits
+ with JsonImplicits
with SearchImplicits
with PostGISImplicits
diff --git a/src/test/scala/com/github/tminglei.slickpg/PgJsonSupportTest.scala b/src/test/scala/com/github/tminglei.slickpg/PgJsonSupportTest.scala
new file mode 100644
index 00000000..f5ea68f1
--- /dev/null
+++ b/src/test/scala/com/github/tminglei.slickpg/PgJsonSupportTest.scala
@@ -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
+ }
+ }
+}