From 226ed09a6c5550e57f9b856ad2840e9a38134a2f Mon Sep 17 00:00:00 2001 From: Stephen Couchman Date: Mon, 25 Apr 2016 18:45:25 -0400 Subject: [PATCH 01/16] Reworked URLParser to process more URLs. Added MySQL URLParser Made URLParser stricter. Corrected test cases using illegal IP addresses. (ip's out of range) Now accepts JDBC style "jdbc:postgresql:dbname" Switched from fragile regex to java.net.URI parsing. Added parameter URL-format decoding. Deprecated ParserURL in PostgreSQL and converted it to an alias to PostgreSQL URLParser. Deprecated to 0.2.20, the version may need to be updated. --- .../mauricio/async/db/Configuration.scala | 2 + .../UnableToParseURLException.scala | 24 ++ .../async/db/util/AbstractURIParser.scala | 175 ++++++++++++ .../async/db/mysql/util/URLParser.scala | 39 +++ .../async/db/mysql/util/URLParserSpec.scala | 264 ++++++++++++++++++ .../db/postgresql/PostgreSQLConnection.scala | 13 +- .../async/db/postgresql/util/ParserURL.scala | 65 ----- .../async/db/postgresql/util/URLParser.scala | 88 ++++-- .../async/db/postgresql/util/package.scala | 29 ++ .../db/postgresql/util/URLParserSpec.scala | 160 ++++++++--- 10 files changed, 726 insertions(+), 133 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnableToParseURLException.scala create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/util/AbstractURIParser.scala create mode 100644 mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/URLParser.scala create mode 100644 mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/util/URLParserSpec.scala delete mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ParserURL.scala create mode 100644 postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/package.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala index b032ac02..cde267cf 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/Configuration.scala @@ -25,6 +25,8 @@ import scala.concurrent.duration._ object Configuration { val DefaultCharset = CharsetUtil.UTF_8 + + @deprecated("Use com.github.mauricio.async.db.postgresql.util.URLParser.DEFAULT or com.github.mauricio.async.db.mysql.util.URLParser.DEFAULT.", since = "0.2.20") val Default = new Configuration("postgres") } diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnableToParseURLException.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnableToParseURLException.scala new file mode 100644 index 00000000..0d2799df --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/exceptions/UnableToParseURLException.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.github.mauricio.async.db.exceptions + +/** + * Thrown to indicate that a URL Parser could not understand the provided URL. + */ +class UnableToParseURLException(message: String, base: Throwable) extends RuntimeException(message, base) { + def this(message: String) = this(message, null) +} \ No newline at end of file diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/AbstractURIParser.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/AbstractURIParser.scala new file mode 100644 index 00000000..e18de6e1 --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/AbstractURIParser.scala @@ -0,0 +1,175 @@ +/* + * Copyright 2016 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.github.mauricio.async.db.util + +import java.net.{URI, URISyntaxException, URLDecoder} +import java.nio.charset.Charset + +import com.github.mauricio.async.db.exceptions.UnableToParseURLException +import com.github.mauricio.async.db.{Configuration, SSLConfiguration} +import org.slf4j.LoggerFactory + +import scala.util.matching.Regex + +/** + * Common parser assisting methods for PG and MySQL URI parsers. + */ +abstract class AbstractURIParser { + import AbstractURIParser._ + + protected val logger = LoggerFactory.getLogger(getClass) + + /** + * Parses out userInfo into a tuple of optional username and password + * + * @param userInfo the optional user info string + * @return a tuple of optional username and password + */ + final protected def parseUserInfo(userInfo: Option[String]): (Option[String], Option[String]) = userInfo.map(_.split(":", 2).toList) match { + case Some(user :: pass :: Nil) ⇒ (Some(user), Some(pass)) + case Some(user :: Nil) ⇒ (Some(user), None) + case _ ⇒ (None, None) + } + + /** + * A Regex that will match the base name of the driver scheme, minus jdbc:. + * Eg: postgres(?:ul)? + */ + protected val SCHEME: Regex + + /** + * The default for this particular URLParser, ie: appropriate and specific to PG or MySQL accordingly + */ + val DEFAULT: Configuration + + + /** + * Parses the provided url and returns a Configuration based upon it. On an error, + * @param url the URL to parse. + * @param charset the charset to use. + * @return a Configuration. + */ + @throws[UnableToParseURLException]("if the URL does not match the expected type, or cannot be parsed for any reason") + def parseOrDie(url: String, + charset: Charset = DEFAULT.charset): Configuration = { + try { + val properties = parse(new URI(url).parseServerAuthority) + + assembleConfiguration(properties, charset) + } catch { + case e: URISyntaxException => + throw new UnableToParseURLException(s"Failed to parse URL: $url", e) + } + } + + + /** + * Parses the provided url and returns a Configuration based upon it. On an error, + * a default configuration is returned. + * @param url the URL to parse. + * @param charset the charset to use. + * @return a Configuration. + */ + def parse(url: String, + charset: Charset = DEFAULT.charset + ): Configuration = { + try { + parseOrDie(url, charset) + } catch { + case e: Exception => + logger.warn(s"Connection url '$url' could not be parsed.", e) + // Fallback to default to maintain current behavior + DEFAULT + } + } + + /** + * Assembles a configuration out of the provided property map. This is the generic form, subclasses may override to + * handle additional properties. + * @param properties the extracted properties from the URL. + * @param charset the charset passed in to parse or parseOrDie. + * @return + */ + protected def assembleConfiguration(properties: Map[String, String], charset: Charset): Configuration = { + DEFAULT.copy( + username = properties.getOrElse(USERNAME, DEFAULT.username), + password = properties.get(PASSWORD), + database = properties.get(DBNAME), + host = properties.getOrElse(HOST, DEFAULT.host), + port = properties.get(PORT).map(_.toInt).getOrElse(DEFAULT.port), + ssl = SSLConfiguration(properties), + charset = charset + ) + } + + + protected def parse(uri: URI): Map[String, String] = { + uri.getScheme match { + case SCHEME() => + val userInfo = parseUserInfo(Option(uri.getUserInfo)) + + val port = Some(uri.getPort).filter(_ > 0) + val db = Option(uri.getPath).map(_.stripPrefix("/")).filterNot(_.isEmpty) + val host = Option(uri.getHost) + + val builder = Map.newBuilder[String, String] + builder ++= userInfo._1.map(USERNAME -> _) + builder ++= userInfo._2.map(PASSWORD -> _) + builder ++= port.map(PORT -> _.toString) + builder ++= db.map(DBNAME -> _) + builder ++= host.map(HOST -> unwrapIpv6address(_)) + + // Parse query string parameters and just append them, overriding anything previously set + builder ++= (for { + qs <- Option(uri.getQuery).toSeq + parameter <- qs.split('&') + Array(name, value) = parameter.split('=') + if name.nonEmpty && value.nonEmpty + } yield URLDecoder.decode(name, "UTF-8") -> URLDecoder.decode(value, "UTF-8")) + + + builder.result + case "jdbc" => + handleJDBC(uri) + case _ => + throw new UnableToParseURLException("Unrecognized URI scheme") + } + } + + /** + * This method breaks out handling of the jdbc: prefixed uri's, allowing them to be handled differently + * without reimplementing all of parse. + */ + protected def handleJDBC(uri: URI): Map[String, String] = parse(new URI(uri.getSchemeSpecificPart)) + + + final protected def unwrapIpv6address(server: String): String = { + if (server.startsWith("[")) { + server.substring(1, server.length() - 1) + } else server + } + +} + +object AbstractURIParser { + // Constants and value names + val PORT = "port" + val DBNAME = "database" + val HOST = "host" + val USERNAME = "user" + val PASSWORD = "password" +} + diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/URLParser.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/URLParser.scala new file mode 100644 index 00000000..ba9c0333 --- /dev/null +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/URLParser.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.github.mauricio.async.db.mysql.util + +import com.github.mauricio.async.db.util.AbstractURIParser +import com.github.mauricio.async.db.Configuration + +/** + * The MySQL URL parser. + */ +object URLParser extends AbstractURIParser { + + /** + * The default configuration for MySQL. + */ + override val DEFAULT = Configuration( + username = "root", + host = "127.0.0.1", //Matched JDBC default + port = 3306, + password = None, + database = None + ) + + override protected val SCHEME = "^mysql$".r + +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/util/URLParserSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/util/URLParserSpec.scala new file mode 100644 index 00000000..b15ab779 --- /dev/null +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/util/URLParserSpec.scala @@ -0,0 +1,264 @@ +/* + * Copyright 2016 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.github.mauricio.async.db.mysql.util + +import java.nio.charset.Charset + +import com.github.mauricio.async.db.{Configuration, SSLConfiguration} +import com.github.mauricio.async.db.exceptions.UnableToParseURLException +import io.netty.buffer.{ByteBufAllocator, PooledByteBufAllocator} +import org.specs2.mutable.Specification + +import scala.concurrent.duration.Duration + +class URLParserSpec extends Specification { + + "mysql URLParser" should { + import URLParser.{DEFAULT, parse, parseOrDie} + + + "have a reasonable default" in { + // This is a deliberate extra step, protecting the DEFAULT from frivilous changes. + // Any change to DEFAULT should require a change to this test. + + DEFAULT === Configuration( + username = "root", + host = "127.0.0.1", //Matched JDBC default + port = 3306, + password = None, + database = None + ) + } + + + // Divided into sections + // =========== jdbc:mysql =========== + + "create a jdbc:mysql connection with the available fields" in { + val connectionUri = "jdbc:mysql://128.167.54.90:9987/my_database?user=john&password=doe" + + parse(connectionUri) === DEFAULT.copy( + username = "john", + password = Some("doe"), + database = Some("my_database"), + host = "128.167.54.90", + port = 9987 + ) + } + + "create a connection without port" in { + val connectionUri = "jdbc:mysql://128.167.54.90/my_database?user=john&password=doe" + + parse(connectionUri) === DEFAULT.copy( + username = "john", + password = Some("doe"), + database = Some("my_database"), + host = "128.167.54.90" + ) + } + + + "create a connection without username and password" in { + val connectionUri = "jdbc:mysql://128.167.54.90:9987/my_database" + + parse(connectionUri) === DEFAULT.copy( + database = Some("my_database"), + host = "128.167.54.90", + port = 9987 + ) + } + + "create a connection from a heroku like URL using 'mysql' protocol" in { + val connectionUri = "mysql://john:doe@128.167.54.90:9987/my_database" + + parse(connectionUri) === DEFAULT.copy( + username = "john", + password = Some("doe"), + database = Some("my_database"), + host = "128.167.54.90", + port = 9987 + ) + } + + "create a connection with the available fields and named server" in { + val connectionUri = "jdbc:mysql://localhost:9987/my_database?user=john&password=doe" + + parse(connectionUri) === DEFAULT.copy( + username = "john", + password = Some("doe"), + database = Some("my_database"), + host = "localhost", + port = 9987 + ) + } + + "create a connection from a heroku like URL with named server" in { + val connectionUri = "mysql://john:doe@psql.heroku.com:9987/my_database" + + val configuration = parse(connectionUri) + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "psql.heroku.com" + configuration.port === 9987 + } + + "create a connection with the available fields and ipv6" in { + val connectionUri = "jdbc:mysql://[::1]:9987/my_database?user=john&password=doe" + + val configuration = parse(connectionUri) + + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "::1" + configuration.port === 9987 + } + + "create a connection from a heroku like URL and with ipv6" in { + val connectionUri = "mysql://john:doe@[::1]:9987/my_database" + + val configuration = parse(connectionUri) + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === Some("my_database") + configuration.host === "::1" + configuration.port === 9987 + } + + "create a connection with a missing hostname" in { + val connectionUri = "jdbc:mysql:/my_database?user=john&password=doe" + + parse(connectionUri) === DEFAULT.copy( + username = "john", + password = Some("doe"), + database = Some("my_database") + ) + } + + "create a connection with a missing database name" in { + val connectionUri = "jdbc:mysql://[::1]:9987/?user=john&password=doe" + + val configuration = parse(connectionUri) + + configuration.username === "john" + configuration.password === Some("doe") + configuration.database === None + configuration.host === "::1" + configuration.port === 9987 + } + + "create a connection with all default fields" in { + val connectionUri = "jdbc:mysql:" + + val configuration = parse(connectionUri) + + configuration.username === "root" + configuration.password === None + configuration.database === None + configuration.host === "127.0.0.1" + configuration.port === 3306 + } + + "create a connection with an empty (invalid) url" in { + val connectionUri = "" + + val configuration = parse(connectionUri) + + configuration.username === "root" + configuration.password === None + configuration.database === None + configuration.host === "127.0.0.1" + configuration.port === 3306 + } + + + "recognise a mysql:// uri" in { + parse("mysql://localhost:425/dbname") mustEqual DEFAULT.copy( + username = "root", + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "recognize a jdbc:mysql:// uri" in { + parse("jdbc:mysql://localhost:425/dbname") mustEqual DEFAULT.copy( + username = "root", + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "pull the username and password from URI credentials" in { + parse("jdbc:mysql://user:password@localhost:425/dbname") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "pull the username and password from query string" in { + parse("jdbc:mysql://localhost:425/dbname?user=user&password=password") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + // Included for consistency, so later changes aren't allowed to change behavior + "use the query string parameters to override URI credentials" in { + parse("jdbc:mysql://baduser:badpass@localhost:425/dbname?user=user&password=password") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "successfully default the port to the mysql port" in { + parse("jdbc:mysql://baduser:badpass@localhost/dbname?user=user&password=password") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 3306, + host = "localhost" + ) + } + + "reject malformed ip addresses" in { + val connectionUri = "mysql://john:doe@128.567.54.90:9987/my_database" + + val configuration = parse(connectionUri) + configuration.username === "root" + configuration.password === None + configuration.database === None + configuration.host === "127.0.0.1" + configuration.port === 3306 + + parseOrDie(connectionUri) must throwA[UnableToParseURLException] + } + + } + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala index 8c58076b..ec89660c 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnection.scala @@ -17,8 +17,8 @@ package com.github.mauricio.async.db.postgresql import com.github.mauricio.async.db.QueryResult -import com.github.mauricio.async.db.column.{ColumnEncoderRegistry, ColumnDecoderRegistry} -import com.github.mauricio.async.db.exceptions.{InsufficientParametersException, ConnectionStillRunningQueryException} +import com.github.mauricio.async.db.column.{ColumnDecoderRegistry, ColumnEncoderRegistry} +import com.github.mauricio.async.db.exceptions.{ConnectionStillRunningQueryException, InsufficientParametersException} import com.github.mauricio.async.db.general.MutableResultSet import com.github.mauricio.async.db.pool.TimeoutScheduler import com.github.mauricio.async.db.postgresql.codec.{PostgreSQLConnectionDelegate, PostgreSQLConnectionHandler} @@ -26,14 +26,17 @@ import com.github.mauricio.async.db.postgresql.column.{PostgreSQLColumnDecoderRe import com.github.mauricio.async.db.postgresql.exceptions._ import com.github.mauricio.async.db.util._ import com.github.mauricio.async.db.{Configuration, Connection} -import java.util.concurrent.atomic.{AtomicLong,AtomicInteger,AtomicReference} +import java.util.concurrent.atomic.{AtomicInteger, AtomicLong, AtomicReference} + import messages.backend._ import messages.frontend._ -import scala.Some + import scala.concurrent._ import io.netty.channel.EventLoopGroup import java.util.concurrent.CopyOnWriteArrayList +import com.github.mauricio.async.db.postgresql.util.URLParser + object PostgreSQLConnection { final val Counter = new AtomicLong() final val ServerVersionKey = "server_version" @@ -42,7 +45,7 @@ object PostgreSQLConnection { class PostgreSQLConnection ( - configuration: Configuration = Configuration.Default, + configuration: Configuration = URLParser.DEFAULT, encoderRegistry: ColumnEncoderRegistry = PostgreSQLColumnEncoderRegistry.Instance, decoderRegistry: ColumnDecoderRegistry = PostgreSQLColumnDecoderRegistry.Instance, group : EventLoopGroup = NettyUtils.DefaultEventLoopGroup, diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ParserURL.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ParserURL.scala deleted file mode 100644 index 8172877e..00000000 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/ParserURL.scala +++ /dev/null @@ -1,65 +0,0 @@ -/** - * - */ -package com.github.mauricio.async.db.postgresql.util - -import org.slf4j.LoggerFactory - -/** - * @author gciuloaica - * - */ -object ParserURL { - - private val logger = LoggerFactory.getLogger(ParserURL.getClass()) - - val PGPORT = "port" - val PGDBNAME = "database" - val PGHOST = "host" - val PGUSERNAME = "user" - val PGPASSWORD = "password" - - val DEFAULT_PORT = "5432" - - private val pgurl1 = """(jdbc:postgresql):(?://([^/:]*|\[.+\])(?::(\d+))?)?(?:/([^/?]*))?(?:\?(.*))?""".r - private val pgurl2 = """(postgres|postgresql)://(.*):(.*)@(.*):(\d+)/([^/?]*)(?:\?(.*))?""".r - - def parse(connectionURL: String): Map[String, String] = { - val properties: Map[String, String] = Map() - - def parseOptions(optionsStr: String): Map[String, String] = - optionsStr.split("&").map { o => - o.span(_ != '=') match { - case (name, value) => name -> value.drop(1) - } - }.toMap - - connectionURL match { - case pgurl1(protocol, server, port, dbname, params) => { - var result = properties - if (server != null) result += (PGHOST -> unwrapIpv6address(server)) - if (dbname != null && dbname.nonEmpty) result += (PGDBNAME -> dbname) - if (port != null) result += (PGPORT -> port) - if (params != null) result ++= parseOptions(params) - result - } - case pgurl2(protocol, username, password, server, port, dbname, params) => { - var result = properties + (PGHOST -> unwrapIpv6address(server)) + (PGPORT -> port) + (PGDBNAME -> dbname) + (PGUSERNAME -> username) + (PGPASSWORD -> password) - if (params != null) result ++= parseOptions(params) - result - } - case _ => { - logger.warn(s"Connection url '$connectionURL' could not be parsed.") - properties - } - } - - } - - private def unwrapIpv6address(server: String): String = { - if (server.startsWith("[")) { - server.substring(1, server.length() - 1) - } else server - } - -} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala index debcb6d9..fcb9b3cf 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/URLParser.scala @@ -1,46 +1,72 @@ -/* - * Copyright 2013 Maurício Linhares +/** * - * Maurício Linhares licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. */ - package com.github.mauricio.async.db.postgresql.util -import com.github.mauricio.async.db.{Configuration, SSLConfiguration} +import java.net.URI import java.nio.charset.Charset -object URLParser { +import com.github.mauricio.async.db.{Configuration, SSLConfiguration} +import com.github.mauricio.async.db.util.AbstractURIParser - import Configuration.Default +/** + * The PostgreSQL URL parser. + */ +object URLParser extends AbstractURIParser { + import AbstractURIParser._ - def parse(url: String, - charset: Charset = Default.charset - ): Configuration = { + // Alias these for anyone still making use of them + @deprecated("Use com.github.mauricio.async.db.AbstractURIParser.PORT", since = "0.2.20") + val PGPORT = PORT - val properties = ParserURL.parse(url) + @deprecated("Use com.github.mauricio.async.db.AbstractURIParser.DBNAME", since = "0.2.20") + val PGDBNAME = DBNAME - val port = properties.get(ParserURL.PGPORT).getOrElse(ParserURL.DEFAULT_PORT).toInt + @deprecated("Use com.github.mauricio.async.db.AbstractURIParser.HOST", since = "0.2.20") + val PGHOST = HOST - new Configuration( - username = properties.get(ParserURL.PGUSERNAME).getOrElse(Default.username), - password = properties.get(ParserURL.PGPASSWORD), - database = properties.get(ParserURL.PGDBNAME), - host = properties.getOrElse(ParserURL.PGHOST, Default.host), - port = port, - ssl = SSLConfiguration(properties), - charset = charset - ) + @deprecated("Use com.github.mauricio.async.db.AbstractURIParser.USERNAME", since = "0.2.20") + val PGUSERNAME = USERNAME + @deprecated("Use com.github.mauricio.async.db.AbstractURIParser.PASSWORD", since = "0.2.20") + val PGPASSWORD = PASSWORD + + @deprecated("Use com.github.mauricio.async.db.postgresql.util.URLParser.DEFAULT.port", since = "0.2.20") + val DEFAULT_PORT = "5432" + + /** + * The default configuration for PostgreSQL. + */ + override val DEFAULT = Configuration( + username = "postgres", + host = "localhost", + port = 5432, + password = None, + database = None, + ssl = SSLConfiguration() + ) + + override protected val SCHEME = "^postgres(?:ql)?$".r + + private val simplePGDB = "^postgresql:(\\w+)$".r + + override protected def handleJDBC(uri: URI): Map[String, String] = uri.getSchemeSpecificPart match { + case simplePGDB(db) => Map(DBNAME -> db) + case x => parse(new URI(x)) } + /** + * Assembles a configuration out of the provided property map. This is the generic form, subclasses may override to + * handle additional properties. + * + * @param properties the extracted properties from the URL. + * @param charset the charset passed in to parse or parseOrDie. + * @return + */ + override protected def assembleConfiguration(properties: Map[String, String], charset: Charset): Configuration = { + // Add SSL Configuration + super.assembleConfiguration(properties, charset).copy( + ssl = SSLConfiguration(properties) + ) + } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/package.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/package.scala new file mode 100644 index 00000000..5d321170 --- /dev/null +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/util/package.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.github.mauricio.async.db.postgresql + +/** + * Contains package level aliases and type renames. + */ +package object util { + + /** + * Alias to help compatibility. + */ + @deprecated("Use com.github.mauricio.async.db.postgresql.util.URLParser", since = "0.2.20") + val ParserURL = URLParser + +} diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala index d0df6eaa..9d2d2828 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/util/URLParserSpec.scala @@ -17,79 +17,93 @@ package com.github.mauricio.async.db.postgresql.util import org.specs2.mutable.Specification -import com.github.mauricio.async.db.Configuration -import com.github.mauricio.async.db.SSLConfiguration import com.github.mauricio.async.db.SSLConfiguration.Mode +import com.github.mauricio.async.db.exceptions.UnableToParseURLException class URLParserSpec extends Specification { - "parser" should { + "postgresql URLParser" should { + import URLParser.{parse, parseOrDie, DEFAULT} - "create a connection with the available fields" in { - val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?user=john&password=doe" + // Divided into sections + // =========== jdbc:postgresql =========== - val configuration = URLParser.parse(connectionUri) + // https://jdbc.postgresql.org/documentation/80/connect.html + "recognize a jdbc:postgresql:dbname uri" in { + val connectionUri = "jdbc:postgresql:dbname" + + parse(connectionUri) mustEqual DEFAULT.copy( + database = Some("dbname") + ) + } + + "create a jdbc:postgresql connection with the available fields" in { + val connectionUri = "jdbc:postgresql://128.167.54.90:9987/my_database?user=john&password=doe" + + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") - configuration.host === "128.567.54.90" + configuration.host === "128.167.54.90" configuration.port === 9987 } "create a connection without port" in { - val connectionUri = "jdbc:postgresql://128.567.54.90/my_database?user=john&password=doe" + val connectionUri = "jdbc:postgresql://128.167.54.90/my_database?user=john&password=doe" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") - configuration.host === "128.567.54.90" + configuration.host === "128.167.54.90" configuration.port === 5432 } "create a connection without username and password" in { - val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database" + val connectionUri = "jdbc:postgresql://128.167.54.90:9987/my_database" - val configuration = URLParser.parse(connectionUri) - configuration.username === Configuration.Default.username + val configuration = parse(connectionUri) + configuration.username === DEFAULT.username configuration.password === None configuration.database === Some("my_database") - configuration.host === "128.567.54.90" + configuration.host === "128.167.54.90" configuration.port === 9987 } + //========== postgresql:// ============== + "create a connection from a heroku like URL using 'postgresql' protocol" in { - val connectionUri = "postgresql://john:doe@128.567.54.90:9987/my_database" + val connectionUri = "postgresql://john:doe@128.167.54.90:9987/my_database" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") - configuration.host === "128.567.54.90" + configuration.host === "128.167.54.90" configuration.port === 9987 } "create a connection with SSL enabled" in { - val connectionUri = "jdbc:postgresql://128.567.54.90:9987/my_database?sslmode=verify-full" + val connectionUri = "jdbc:postgresql://128.167.54.90:9987/my_database?sslmode=verify-full" - val configuration = URLParser.parse(connectionUri) - configuration.username === Configuration.Default.username + val configuration = parse(connectionUri) + configuration.username === DEFAULT.username configuration.password === None configuration.database === Some("my_database") - configuration.host === "128.567.54.90" + configuration.host === "128.167.54.90" configuration.port === 9987 configuration.ssl.mode === Mode.VerifyFull } "create a connection with SSL enabled and root CA from a heroku like URL using 'postgresql' protocol" in { - val connectionUri = "postgresql://john:doe@128.567.54.90:9987/my_database?sslmode=verify-ca&sslrootcert=server.crt" + val connectionUri = "postgresql://john:doe@128.167.54.90:9987/my_database?sslmode=verify-ca&sslrootcert=server.crt" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") - configuration.host === "128.567.54.90" + configuration.host === "128.167.54.90" configuration.port === 9987 configuration.ssl.mode === Mode.VerifyCA configuration.ssl.rootCert.map(_.getPath) === Some("server.crt") @@ -98,7 +112,7 @@ class URLParserSpec extends Specification { "create a connection with the available fields and named server" in { val connectionUri = "jdbc:postgresql://localhost:9987/my_database?user=john&password=doe" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") @@ -109,7 +123,7 @@ class URLParserSpec extends Specification { "create a connection from a heroku like URL with named server" in { val connectionUri = "postgresql://john:doe@psql.heroku.com:9987/my_database" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") @@ -120,7 +134,7 @@ class URLParserSpec extends Specification { "create a connection with the available fields and ipv6" in { val connectionUri = "jdbc:postgresql://[::1]:9987/my_database?user=john&password=doe" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") @@ -132,7 +146,7 @@ class URLParserSpec extends Specification { "create a connection from a heroku like URL and with ipv6" in { val connectionUri = "postgresql://john:doe@[::1]:9987/my_database" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") configuration.database === Some("my_database") @@ -143,7 +157,7 @@ class URLParserSpec extends Specification { "create a connection with a missing hostname" in { val connectionUri = "jdbc:postgresql:/my_database?user=john&password=doe" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") @@ -155,7 +169,7 @@ class URLParserSpec extends Specification { "create a connection with a missing database name" in { val connectionUri = "jdbc:postgresql://[::1]:9987/?user=john&password=doe" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "john" configuration.password === Some("doe") @@ -167,7 +181,7 @@ class URLParserSpec extends Specification { "create a connection with all default fields" in { val connectionUri = "jdbc:postgresql:" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "postgres" configuration.password === None @@ -179,7 +193,7 @@ class URLParserSpec extends Specification { "create a connection with an empty (invalid) url" in { val connectionUri = "" - val configuration = URLParser.parse(connectionUri) + val configuration = parse(connectionUri) configuration.username === "postgres" configuration.password === None @@ -188,6 +202,88 @@ class URLParserSpec extends Specification { configuration.port === 5432 } + + "recognise a postgresql:// uri" in { + parse("postgresql://localhost:425/dbname") mustEqual DEFAULT.copy( + username = "postgres", + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "recognise a postgres:// uri" in { + parse("postgres://localhost:425/dbname") mustEqual DEFAULT.copy( + username = "postgres", + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "recognize a jdbc:postgresql:// uri" in { + parse("jdbc:postgresql://localhost:425/dbname") mustEqual DEFAULT.copy( + username = "postgres", + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "pull the username and password from URI credentials" in { + parse("jdbc:postgresql://user:password@localhost:425/dbname") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "pull the username and password from query string" in { + parse("jdbc:postgresql://localhost:425/dbname?user=user&password=password") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + // Included for consistency, so later changes aren't allowed to change behavior + "use the query string parameters to override URI credentials" in { + parse("jdbc:postgresql://baduser:badpass@localhost:425/dbname?user=user&password=password") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 425, + host = "localhost" + ) + } + + "successfully default the port to the PostgreSQL port" in { + parse("jdbc:postgresql://baduser:badpass@localhost/dbname?user=user&password=password") mustEqual DEFAULT.copy( + username = "user", + password = Some("password"), + database = Some("dbname"), + port = 5432, + host = "localhost" + ) + } + + "reject malformed ip addresses" in { + val connectionUri = "postgresql://john:doe@128.567.54.90:9987/my_database" + + val configuration = parse(connectionUri) + configuration.username === "postgres" + configuration.password === None + configuration.database === None + configuration.host === "localhost" + configuration.port === 5432 + + parseOrDie(connectionUri) must throwA[UnableToParseURLException] + } + } } From d51a85b9ffa96b91f79372c22353ad4073282830 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 Jun 2016 00:13:13 -0400 Subject: [PATCH 02/16] Closing 0.2.20 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index e02f3b3e..f771f803 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -49,7 +49,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.20-SNAPSHOT" + val commonVersion = "0.2.20" val projectScalaVersion = "2.11.7" val specs2Version = "2.5" From 7dc83b91c153b74a1c94329ada43b3e15c51bb7f Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Fri, 10 Jun 2016 00:23:03 -0400 Subject: [PATCH 03/16] Starting next development cycle --- README.markdown | 8 ++++---- project/Build.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index b87583a2..9977b309 100644 --- a/README.markdown +++ b/README.markdown @@ -54,7 +54,7 @@ You can view the project's [CHANGELOG here](CHANGELOG.md). And if you're in a hurry, you can include them in your build like this, if you're using PostgreSQL: ```scala -"com.github.mauricio" %% "postgresql-async" % "0.2.19" +"com.github.mauricio" %% "postgresql-async" % "0.2.20" ``` Or Maven: @@ -63,14 +63,14 @@ Or Maven: com.github.mauricio postgresql-async_2.11 - 0.2.19 + 0.2.20 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.19" +"com.github.mauricio" %% "mysql-async" % "0.2.20" ``` Or Maven: @@ -79,7 +79,7 @@ Or Maven: com.github.mauricio mysql-async_2.11 - 0.2.19 + 0.2.20 ``` diff --git a/project/Build.scala b/project/Build.scala index f771f803..ca5bcb9e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -49,7 +49,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.20" + val commonVersion = "0.2.21-SNAPSHOT" val projectScalaVersion = "2.11.7" val specs2Version = "2.5" From 3d0fdef82de66d9c804ceab712013bf1d44e908a Mon Sep 17 00:00:00 2001 From: volth Date: Fri, 8 Jul 2016 16:47:37 +0000 Subject: [PATCH 04/16] Support java.net.InetAddress (encoding and decoding) and user-defined types (encoding only) --- .../db/column/InetAddressEncoderDecoder.scala | 36 ++++++ .../db/postgresql/column/ColumnTypes.scala | 2 + .../PostgreSQLColumnDecoderRegistry.scala | 4 + .../PostgreSQLColumnEncoderRegistry.scala | 49 +++++---- .../async/db/postgresql/ArrayTypesSpec.scala | 103 ++++++++++++------ 5 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 db-async-common/src/main/scala/com/github/mauricio/async/db/column/InetAddressEncoderDecoder.scala diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/column/InetAddressEncoderDecoder.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/InetAddressEncoderDecoder.scala new file mode 100644 index 00000000..ecac853d --- /dev/null +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/column/InetAddressEncoderDecoder.scala @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Maurício Linhares + * + * Maurício Linhares licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.github.mauricio.async.db.column + +import java.net.InetAddress +import sun.net.util.IPAddressUtil.{textToNumericFormatV4,textToNumericFormatV6} + +object InetAddressEncoderDecoder extends ColumnEncoderDecoder { + + override def decode(value: String): Any = { + if (value contains ':') { + InetAddress.getByAddress(textToNumericFormatV6(value)) + } else { + InetAddress.getByAddress(textToNumericFormatV4(value)) + } + } + + override def encode(value: Any): String = { + value.asInstanceOf[InetAddress].getHostAddress + } + +} diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala index 29c6b736..93fef482 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ColumnTypes.scala @@ -67,6 +67,8 @@ object ColumnTypes { final val UUIDArray = 2951 final val XMLArray = 143 + final val Inet = 869 + final val InetArray = 1041 } /* diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala index 606bb442..5b4a47a7 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnDecoderRegistry.scala @@ -46,6 +46,7 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e private final val timeWithTimestampArrayDecoder = new ArrayDecoder(TimeWithTimezoneEncoderDecoder) private final val intervalArrayDecoder = new ArrayDecoder(PostgreSQLIntervalEncoderDecoder) private final val uuidArrayDecoder = new ArrayDecoder(UUIDEncoderDecoder) + private final val inetAddressArrayDecoder = new ArrayDecoder(InetAddressEncoderDecoder) override def decode(kind: ColumnData, value: ByteBuf, charset: Charset): Any = { decoderFor(kind.dataType).decode(kind, value, charset) @@ -114,6 +115,9 @@ class PostgreSQLColumnDecoderRegistry( charset : Charset = CharsetUtil.UTF_8 ) e case XMLArray => this.stringArrayDecoder case ByteA => ByteArrayEncoderDecoder + case Inet => InetAddressEncoderDecoder + case InetArray => this.inetAddressArrayDecoder + case _ => StringEncoderDecoder } } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnEncoderRegistry.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnEncoderRegistry.scala index 5292839c..c9f95f43 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnEncoderRegistry.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/PostgreSQLColumnEncoderRegistry.scala @@ -52,6 +52,8 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { classOf[BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), classOf[java.math.BigDecimal] -> (BigDecimalEncoderDecoder -> ColumnTypes.Numeric), + classOf[java.net.InetAddress] -> (InetAddressEncoderDecoder -> ColumnTypes.Inet), + classOf[java.util.UUID] -> (UUIDEncoderDecoder -> ColumnTypes.UUID), classOf[LocalDate] -> ( DateEncoderDecoder -> ColumnTypes.Date ), @@ -104,17 +106,12 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { if (encoder.isDefined) { encoder.get._1.encode(value) } else { - - val view: Option[Traversable[Any]] = value match { - case i: java.lang.Iterable[_] => Some(i.toIterable) - case i: Traversable[_] => Some(i) - case i: Array[_] => Some(i.toIterable) - case _ => None - } - - view match { - case Some(collection) => encodeArray(collection) - case None => { + value match { + case i: java.lang.Iterable[_] => encodeArray(i.toIterable) + case i: Traversable[_] => encodeArray(i) + case i: Array[_] => encodeArray(i.toIterable) + case p: Product => encodeComposite(p) + case _ => { this.classesSequence.find(entry => entry._1.isAssignableFrom(value.getClass)) match { case Some(parent) => parent._2._1.encode(value) case None => value.toString @@ -126,14 +123,9 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { } - private def encodeArray(collection: Traversable[_]): String = { - val builder = new StringBuilder() - - builder.append('{') - - val result = collection.map { + private def encodeComposite(p: Product): String = { + p.productIterator.map { item => - if (item == null || item == None) { "NULL" } else { @@ -143,13 +135,22 @@ class PostgreSQLColumnEncoderRegistry extends ColumnEncoderRegistry { this.encode(item) } } + }.mkString("(", ",", ")") + } - }.mkString(",") - - builder.append(result) - builder.append('}') - - builder.toString() + private def encodeArray(collection: Traversable[_]): String = { + collection.map { + item => + if (item == null || item == None) { + "NULL" + } else { + if (this.shouldQuote(item)) { + "\"" + this.encode(item).replaceAllLiterally("\\", """\\""").replaceAllLiterally("\"", """\"""") + "\"" + } else { + this.encode(item) + } + } + }.mkString("{", ",", "}") } private def shouldQuote(value: Any): Boolean = { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala index e941e145..5391588c 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/ArrayTypesSpec.scala @@ -16,31 +16,45 @@ package com.github.mauricio.async.db.postgresql -import com.github.mauricio.async.db.column.TimestampWithTimezoneEncoderDecoder +import com.github.mauricio.async.db.column.{TimestampWithTimezoneEncoderDecoder, InetAddressEncoderDecoder} import org.specs2.mutable.Specification +import java.net.InetAddress class ArrayTypesSpec extends Specification with DatabaseTestHelper { - - val simpleCreate = """create temp table type_test_table ( - bigserial_column bigserial not null, - smallint_column integer[] not null, - text_column text[] not null, - timestamp_column timestamp with time zone[] not null, - constraint bigserial_column_pkey primary key (bigserial_column) - )""" + // `uniq` allows sbt to run the tests concurrently as there is no CREATE TEMP TYPE + def simpleCreate(uniq: String) = s"""DROP TYPE IF EXISTS dir_$uniq; + CREATE TYPE direction_$uniq AS ENUM ('in','out'); + DROP TYPE IF EXISTS endpoint_$uniq; + CREATE TYPE endpoint_$uniq AS (ip inet, port integer); + create temp table type_test_table_$uniq ( + bigserial_column bigserial not null, + smallint_column integer[] not null, + text_column text[] not null, + inet_column inet[] not null, + direction_column direction_$uniq[] not null, + endpoint_column endpoint_$uniq[] not null, + timestamp_column timestamp with time zone[] not null, + constraint bigserial_column_pkey primary key (bigserial_column) + )""" + def simpleDrop(uniq: String) = s"""drop table if exists type_test_table_$uniq; + drop type if exists endpoint_$uniq; + drop type if exists direction_$uniq""" val insert = - """insert into type_test_table - (smallint_column, text_column, timestamp_column) + """insert into type_test_table_cptat + (smallint_column, text_column, inet_column, direction_column, endpoint_column, timestamp_column) values ( '{1,2,3,4}', '{"some,\"comma,separated,text","another line of text","fake\,backslash","real\\,backslash\\",NULL}', + '{"127.0.0.1","2002:15::1"}', + '{"in","out"}', + '{"(\"127.0.0.1\",80)","(\"2002:15::1\",443)"}', '{"2013-04-06 01:15:10.528-03","2013-04-06 01:15:08.528-03"}' )""" - val insertPreparedStatement = """insert into type_test_table - (smallint_column, text_column, timestamp_column) - values (?,?,?)""" + val insertPreparedStatement = """insert into type_test_table_csaups + (smallint_column, text_column, inet_column, direction_column, endpoint_column, timestamp_column) + values (?,?,?,?,?,?)""" "connection" should { @@ -48,41 +62,62 @@ class ArrayTypesSpec extends Specification with DatabaseTestHelper { withHandler { handler => - executeDdl(handler, simpleCreate) - executeDdl(handler, insert, 1) - val result = executeQuery(handler, "select * from type_test_table").rows.get - result(0)("smallint_column") === List(1,2,3,4) - result(0)("text_column") === List("some,\"comma,separated,text", "another line of text", "fake,backslash", "real\\,backslash\\", null ) - result(0)("timestamp_column") === List( - TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:10.528-03"), - TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") - ) + try { + executeDdl(handler, simpleCreate("cptat")) + executeDdl(handler, insert, 1) + val result = executeQuery(handler, "select * from type_test_table_cptat").rows.get + result(0)("smallint_column") === List(1,2,3,4) + result(0)("text_column") === List("some,\"comma,separated,text", "another line of text", "fake,backslash", "real\\,backslash\\", null ) + result(0)("timestamp_column") === List( + TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:10.528-03"), + TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") + ) + } finally { + executeDdl(handler, simpleDrop("cptat")) + } } } "correctly send arrays using prepared statements" in { + case class Endpoint(ip: InetAddress, port: Int) val timestamps = List( TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:10.528-03"), TimestampWithTimezoneEncoderDecoder.decode("2013-04-06 01:15:08.528-03") ) + val inets = List( + InetAddressEncoderDecoder.decode("127.0.0.1"), + InetAddressEncoderDecoder.decode("2002:15::1") + ) + val directions = List("in", "out") + val endpoints = List( + Endpoint(InetAddress.getByName("127.0.0.1"), 80), // case class + (InetAddress.getByName("2002:15::1"), 443) // tuple + ) val numbers = List(1,2,3,4) val texts = List("some,\"comma,separated,text", "another line of text", "fake,backslash", "real\\,backslash\\", null ) withHandler { handler => - executeDdl(handler, simpleCreate) - executePreparedStatement( - handler, - this.insertPreparedStatement, - Array( numbers, texts, timestamps ) ) - - val result = executeQuery(handler, "select * from type_test_table").rows.get - - result(0)("smallint_column") === numbers - result(0)("text_column") === texts - result(0)("timestamp_column") === timestamps + try { + executeDdl(handler, simpleCreate("csaups")) + executePreparedStatement( + handler, + this.insertPreparedStatement, + Array( numbers, texts, inets, directions, endpoints, timestamps ) ) + + val result = executeQuery(handler, "select * from type_test_table_csaups").rows.get + + result(0)("smallint_column") === numbers + result(0)("text_column") === texts + result(0)("inet_column") === inets + result(0)("direction_column") === "{in,out}" // user type decoding not supported + result(0)("endpoint_column") === """{"(127.0.0.1,80)","(2002:15::1,443)"}""" // user type decoding not supported + result(0)("timestamp_column") === timestamps + } finally { + executeDdl(handler, simpleDrop("csaups")) + } } } From 42ea62150132fe93f709d05384a6c52b64592d30 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Sat, 24 Sep 2016 14:34:03 +0900 Subject: [PATCH 05/16] update specs2 3.8.5 --- .../mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala | 3 ++- .../github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala | 1 + .../github/mauricio/async/db/mysql/StoredProceduresSpec.scala | 3 ++- .../com/github/mauricio/async/db/mysql/TransactionSpec.scala | 1 + .../async/db/postgresql/PostgreSQLConnectionSpec.scala | 3 ++- .../mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala | 1 + project/Build.scala | 3 ++- 7 files changed, 11 insertions(+), 4 deletions(-) diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala index 34ca0662..7c8bfdc4 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/AbstractAsyncObjectPoolSpec.scala @@ -10,7 +10,8 @@ import scala.util.Failure import scala.reflect.runtime.universe.TypeTag import scala.util.Try -import scala.concurrent.duration.{Duration, SECONDS} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ /** * This spec is designed abstract to allow testing of any implementation of AsyncObjectPool, against the common diff --git a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala index acc952e7..0c6d85b4 100644 --- a/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala +++ b/db-async-common/src/test/scala/com/github/mauricio/async/db/pool/TimeoutSchedulerSpec.scala @@ -18,6 +18,7 @@ package com.github.mauricio.async.db.pool import java.util.concurrent.{ScheduledFuture, TimeoutException} import com.github.mauricio.async.db.util.{ByteBufferUtils, ExecutorServiceUtils} import org.specs2.mutable.SpecificationWithJUnit +import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.concurrent.{Future, Promise} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/StoredProceduresSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/StoredProceduresSpec.scala index 3d68563b..d8ff2142 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/StoredProceduresSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/StoredProceduresSpec.scala @@ -19,6 +19,7 @@ package com.github.mauricio.async.db.mysql import com.github.mauricio.async.db.ResultSet import com.github.mauricio.async.db.util.FutureUtils._ import org.specs2.mutable.Specification +import scala.concurrent.ExecutionContext.Implicits.global class StoredProceduresSpec extends Specification with ConnectionHelper { @@ -129,4 +130,4 @@ class StoredProceduresSpec extends Specification with ConnectionHelper { } } } -} \ No newline at end of file +} diff --git a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/TransactionSpec.scala b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/TransactionSpec.scala index 0ef2f86b..83548c9b 100644 --- a/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/TransactionSpec.scala +++ b/mysql-async/src/test/scala/com/github/mauricio/async/db/mysql/TransactionSpec.scala @@ -10,6 +10,7 @@ import com.github.mauricio.async.db.Connection import scala.concurrent.duration.Duration import scala.concurrent.{Await, Future} +import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Success, Failure} object TransactionSpec { diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala index 2843e95e..0e050477 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/PostgreSQLConnectionSpec.scala @@ -30,6 +30,7 @@ import org.specs2.mutable.Specification import scala.concurrent.duration._ import scala.concurrent.{Await, Future} +import scala.concurrent.ExecutionContext.Implicits.global object PostgreSQLConnectionSpec { val log = Log.get[PostgreSQLConnectionSpec] @@ -154,7 +155,7 @@ class PostgreSQLConnectionSpec extends Specification with DatabaseTestHelper { row(10) === DateEncoderDecoder.decode("1984-08-06") row(11) === TimeEncoderDecoder.Instance.decode("22:13:45.888888") row(12) === true - row(13) must beAnInstanceOf[java.lang.Long] + row(13).asInstanceOf[AnyRef] must beAnInstanceOf[java.lang.Long] row(13).asInstanceOf[Long] must beGreaterThan(0L) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala index b71ebe65..c2471a75 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/ConnectionPoolSpec.scala @@ -22,6 +22,7 @@ import com.github.mauricio.async.db.pool.{ConnectionPool, PoolConfiguration} import com.github.mauricio.async.db.postgresql.exceptions.GenericDatabaseException import com.github.mauricio.async.db.postgresql.{PostgreSQLConnection, DatabaseTestHelper} import org.specs2.mutable.Specification +import scala.concurrent.ExecutionContext.Implicits.global object ConnectionPoolSpec { val Insert = "insert into transaction_test (id) values (?)" diff --git a/project/Build.scala b/project/Build.scala index ca5bcb9e..f4fbb02a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -51,7 +51,7 @@ object Configuration { val commonVersion = "0.2.21-SNAPSHOT" val projectScalaVersion = "2.11.7" - val specs2Version = "2.5" + val specs2Version = "3.8.5" val specs2Dependency = "org.specs2" %% "specs2-core" % specs2Version % "test" val specs2JunitDependency = "org.specs2" %% "specs2-junit" % specs2Version % "test" @@ -82,6 +82,7 @@ object Configuration { :+ Opts.compile.unchecked :+ "-feature" , + testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "sequential"), scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), crossScalaVersions := Seq(projectScalaVersion, "2.10.6"), javacOptions := Seq("-source", "1.6", "-target", "1.6", "-encoding", "UTF8"), From 1b49d11ea458b48b3cdb975ebc5463456db1362f Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Sat, 24 Sep 2016 14:29:33 +0900 Subject: [PATCH 06/16] use List instead of Stack Stack in deprecated https://github.com/scala/scala/commit/44a22d7cc0c315b9feaee1d4cb5df7a66578b1ea --- .../async/db/pool/SingleThreadedAsyncObjectPool.scala | 10 ++++++---- .../async/db/postgresql/column/ArrayDecoder.scala | 9 +++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala index 49f60593..b4f25ae2 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/pool/SingleThreadedAsyncObjectPool.scala @@ -22,7 +22,7 @@ import com.github.mauricio.async.db.util.{Log, Worker} import java.util.concurrent.atomic.AtomicLong import java.util.{Timer, TimerTask} -import scala.collection.mutable.{ArrayBuffer, Queue, Stack} +import scala.collection.mutable.{ArrayBuffer, Queue} import scala.concurrent.{Future, Promise} import scala.util.{Failure, Success} @@ -52,7 +52,7 @@ class SingleThreadedAsyncObjectPool[T]( import SingleThreadedAsyncObjectPool.{Counter, log} private val mainPool = Worker() - private var poolables = new Stack[PoolableHolder[T]]() + private var poolables = List.empty[PoolableHolder[T]] private val checkouts = new ArrayBuffer[T](configuration.maxObjects) private val waitQueue = new Queue[Promise[T]]() private val timer = new Timer("async-object-pool-timer-" + Counter.incrementAndGet(), true) @@ -171,7 +171,7 @@ class SingleThreadedAsyncObjectPool[T]( */ private def addBack(item: T, promise: Promise[AsyncObjectPool[T]]) { - this.poolables.push(new PoolableHolder[T](item)) + this.poolables ::= new PoolableHolder[T](item) if (this.waitQueue.nonEmpty) { this.checkout(this.waitQueue.dequeue()) @@ -226,7 +226,9 @@ class SingleThreadedAsyncObjectPool[T]( case e: Exception => promise.failure(e) } } else { - val item = this.poolables.pop().item + val h :: t = this.poolables + this.poolables = t + val item = h.item this.checkouts += item promise.success(item) } diff --git a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala index d69eeba4..b62e9629 100644 --- a/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala +++ b/postgresql-async/src/main/scala/com/github/mauricio/async/db/postgresql/column/ArrayDecoder.scala @@ -19,7 +19,7 @@ package com.github.mauricio.async.db.postgresql.column import com.github.mauricio.async.db.column.ColumnDecoder import com.github.mauricio.async.db.postgresql.util.{ArrayStreamingParserDelegate, ArrayStreamingParser} import scala.collection.IndexedSeq -import scala.collection.mutable.{ArrayBuffer, Stack} +import scala.collection.mutable.ArrayBuffer import com.github.mauricio.async.db.general.ColumnData import io.netty.buffer.{Unpooled, ByteBuf} import java.nio.charset.Charset @@ -32,12 +32,13 @@ class ArrayDecoder(private val decoder: ColumnDecoder) extends ColumnDecoder { buffer.readBytes(bytes) val value = new String(bytes, charset) - val stack = new Stack[ArrayBuffer[Any]]() + var stack = List.empty[ArrayBuffer[Any]] var current: ArrayBuffer[Any] = null var result: IndexedSeq[Any] = null val delegate = new ArrayStreamingParserDelegate { override def arrayEnded { - result = stack.pop() + result = stack.head + stack = stack.tail } override def elementFound(element: String) { @@ -63,7 +64,7 @@ class ArrayDecoder(private val decoder: ColumnDecoder) extends ColumnDecoder { case None => {} } - stack.push(current) + stack ::= current } } From 630c65930f6837ebc5ee5d93314e1fc70512a1e2 Mon Sep 17 00:00:00 2001 From: Sergey Samoylov Date: Fri, 30 Sep 2016 14:06:04 +0300 Subject: [PATCH 07/16] Fix for CLIENT_MULTI_RESULTS constant value --- .../scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala index 4587eb09..3b56ecc0 100644 --- a/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala +++ b/mysql-async/src/main/scala/com/github/mauricio/async/db/mysql/util/MySQLIO.scala @@ -21,7 +21,7 @@ object MySQLIO { final val CLIENT_PROTOCOL_41 = 0x0200 final val CLIENT_CONNECT_WITH_DB = 0x0008 final val CLIENT_TRANSACTIONS = 0x2000 - final val CLIENT_MULTI_RESULTS = 0x200000 + final val CLIENT_MULTI_RESULTS = 0x20000 final val CLIENT_LONG_FLAG = 0x0001 final val CLIENT_PLUGIN_AUTH = 0x00080000 final val CLIENT_SECURE_CONNECTION = 0x00008000 From 2a2896fd22e8e833ba6deca1e7d85944a060f7b0 Mon Sep 17 00:00:00 2001 From: golem131 Date: Sat, 5 Nov 2016 14:02:11 +0300 Subject: [PATCH 08/16] Fix deprecation warning "constructor Slf4JLoggerFactory in class Slf4JLoggerFactory is deprecated: see corresponding Javadoc for more information" --- .../scala/com/github/mauricio/async/db/util/NettyUtils.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/NettyUtils.scala b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/NettyUtils.scala index 32f736e3..c9e09f1a 100644 --- a/db-async-common/src/main/scala/com/github/mauricio/async/db/util/NettyUtils.scala +++ b/db-async-common/src/main/scala/com/github/mauricio/async/db/util/NettyUtils.scala @@ -20,7 +20,7 @@ import io.netty.util.internal.logging.{InternalLoggerFactory, Slf4JLoggerFactory object NettyUtils { - InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) + InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE) lazy val DefaultEventLoopGroup = new NioEventLoopGroup(0, DaemonThreadsFactory("db-async-netty")) } \ No newline at end of file From 4b6c380a35de8ee242188f58c3b7e71fad47917c Mon Sep 17 00:00:00 2001 From: golem131 Date: Mon, 7 Nov 2016 12:39:47 +0300 Subject: [PATCH 09/16] Wait until connection return to pool --- .../SingleThreadedAsyncObjectPoolSpec.scala | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala index d99a60d1..75da1ebd 100644 --- a/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala +++ b/postgresql-async/src/test/scala/com/github/mauricio/async/db/postgresql/pool/SingleThreadedAsyncObjectPoolSpec.scala @@ -16,12 +16,14 @@ package com.github.mauricio.async.db.postgresql.pool -import com.github.mauricio.async.db.pool.{SingleThreadedAsyncObjectPool, PoolExhaustedException, PoolConfiguration} +import com.github.mauricio.async.db.pool.{AsyncObjectPool, PoolConfiguration, PoolExhaustedException, SingleThreadedAsyncObjectPool} import com.github.mauricio.async.db.postgresql.{DatabaseTestHelper, PostgreSQLConnection} import java.nio.channels.ClosedChannelException import java.util.concurrent.TimeUnit + import org.specs2.mutable.Specification -import scala.concurrent.Await + +import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.language.postfixOps import com.github.mauricio.async.db.exceptions.ConnectionStillRunningQueryException @@ -47,23 +49,36 @@ class SingleThreadedAsyncObjectPoolSpec extends Specification with DatabaseTestH pool => val connection = get(pool) - val promises = List(pool.take, pool.take, pool.take) + val promises: List[Future[PostgreSQLConnection]] = List(pool.take, pool.take, pool.take) pool.availables.size === 0 pool.inUse.size === 1 + pool.queued.size must be_<=(3) + + /* pool.take call checkout that call this.mainPool.action, + so enqueuePromise called in executorService, + so there is no guaranties that all promises in queue at that moment + */ + val deadline = 5.seconds.fromNow + while(pool.queued.size < 3 || deadline.hasTimeLeft) { + Thread.sleep(50) + } + pool.queued.size === 3 executeTest(connection) pool.giveBack(connection) - promises.foreach { + val pools: List[Future[AsyncObjectPool[PostgreSQLConnection]]] = promises.map { promise => val connection = Await.result(promise, Duration(5, TimeUnit.SECONDS)) executeTest(connection) pool.giveBack(connection) } + Await.ready(pools.last, Duration(5, TimeUnit.SECONDS)) + pool.availables.size === 1 pool.inUse.size === 0 pool.queued.size === 0 From f75679dd4a9e200636614122a73a18f876a56129 Mon Sep 17 00:00:00 2001 From: golem131 Date: Thu, 3 Nov 2016 17:29:11 +0300 Subject: [PATCH 10/16] Scala 2.12.1 support --- .travis.yml | 6 ++++++ project/Build.scala | 4 ++-- project/build.properties | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c1a7a84..378c49d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,12 @@ scala: jdk: - oraclejdk7 - oraclejdk8 + +matrix: + include: + - scala: 2.12.1 + jdk: oraclejdk8 + services: - postgresql - mysql diff --git a/project/Build.scala b/project/Build.scala index f4fbb02a..e1de52d9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -51,7 +51,7 @@ object Configuration { val commonVersion = "0.2.21-SNAPSHOT" val projectScalaVersion = "2.11.7" - val specs2Version = "3.8.5" + val specs2Version = "3.8.6" val specs2Dependency = "org.specs2" %% "specs2-core" % specs2Version % "test" val specs2JunitDependency = "org.specs2" %% "specs2-junit" % specs2Version % "test" @@ -84,7 +84,7 @@ object Configuration { , testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "sequential"), scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), - crossScalaVersions := Seq(projectScalaVersion, "2.10.6"), + crossScalaVersions := Seq(projectScalaVersion, "2.10.6", "2.12.1"), javacOptions := Seq("-source", "1.6", "-target", "1.6", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, diff --git a/project/build.properties b/project/build.properties index d638b4f3..e0cbc71d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 0.13.8 \ No newline at end of file +sbt.version = 0.13.13 \ No newline at end of file From 2f4444e745c1d1164f6f78ab3244de16593c1a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Mon, 9 Jan 2017 10:42:11 -0300 Subject: [PATCH 11/16] preparing for 0.2.21 --- CHANGELOG.md | 4 ++++ Vagrantfile | 13 ------------- project/Build.scala | 14 +++++++------- project/plugins.sbt | 4 +++- 4 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 Vagrantfile diff --git a/CHANGELOG.md b/CHANGELOG.md index bafc831d..9ac99d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ # Changelog +## 0.2.20 - 2017-09-17 + +* Building for Scala 2.12; + ## 0.2.19 - 2016-03-17 * Always use `NUMERIC` when handling numbers in prepared statements in PostgreSQL; diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 5498f80c..00000000 --- a/Vagrantfile +++ /dev/null @@ -1,13 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - - config.vm.box = "chef/centos-6.5" - config.vm.provision :shell, path: "bootstrap.sh" - config.vm.network :forwarded_port, host: 3307, guest: 3306 - -end diff --git a/project/Build.scala b/project/Build.scala index e1de52d9..86ac4278 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -50,20 +50,20 @@ object ProjectBuild extends Build { object Configuration { val commonVersion = "0.2.21-SNAPSHOT" - val projectScalaVersion = "2.11.7" + val projectScalaVersion = "2.12.1" val specs2Version = "3.8.6" val specs2Dependency = "org.specs2" %% "specs2-core" % specs2Version % "test" val specs2JunitDependency = "org.specs2" %% "specs2-junit" % specs2Version % "test" val specs2MockDependency = "org.specs2" %% "specs2-mock" % specs2Version % "test" - val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.6" % "test" + val logbackDependency = "ch.qos.logback" % "logback-classic" % "1.1.8" % "test" val commonDependencies = Seq( - "org.slf4j" % "slf4j-api" % "1.7.18", - "joda-time" % "joda-time" % "2.9.2", + "org.slf4j" % "slf4j-api" % "1.7.22", + "joda-time" % "joda-time" % "2.9.7", "org.joda" % "joda-convert" % "1.8.1", - "io.netty" % "netty-all" % "4.1.1.Final", - "org.javassist" % "javassist" % "3.20.0-GA", + "io.netty" % "netty-all" % "4.1.6.Final", + "org.javassist" % "javassist" % "3.21.0-GA", specs2Dependency, specs2JunitDependency, specs2MockDependency, @@ -84,7 +84,7 @@ object Configuration { , testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "sequential"), scalacOptions in doc := Seq("-doc-external-doc:scala=http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"), - crossScalaVersions := Seq(projectScalaVersion, "2.10.6", "2.12.1"), + crossScalaVersions := Seq(projectScalaVersion, "2.10.6", "2.11.8"), javacOptions := Seq("-source", "1.6", "-target", "1.6", "-encoding", "UTF8"), organization := "com.github.mauricio", version := commonVersion, diff --git a/project/plugins.sbt b/project/plugins.sbt index 4528f2d6..d271b7f7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,8 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0") addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") + +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.0") resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" From 94a7ae428840e5ef948c8307591ab526521c753b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Mon, 9 Jan 2017 11:14:22 -0300 Subject: [PATCH 12/16] Remove JDK7 from build targets --- .travis.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 378c49d0..3e334f1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,15 +2,11 @@ language: scala scala: - 2.10.4 - 2.11.7 + - 2.12.1 + jdk: - - oraclejdk7 - oraclejdk8 -matrix: - include: - - scala: 2.12.1 - jdk: oraclejdk8 - services: - postgresql - mysql From b62199294a01f6c350835b9b22fbb4954bbf3195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Mon, 9 Jan 2017 13:53:30 -0300 Subject: [PATCH 13/16] Closing 0.2.21 --- CHANGELOG.md | 1 + project/Build.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac99d03..ce4b61ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ ## 0.2.20 - 2017-09-17 * Building for Scala 2.12; +* Fix SFL4J deprecation warning - #201 - @golem131; ## 0.2.19 - 2016-03-17 diff --git a/project/Build.scala b/project/Build.scala index 86ac4278..f70240ff 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -49,7 +49,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.21-SNAPSHOT" + val commonVersion = "0.2.21" val projectScalaVersion = "2.12.1" val specs2Version = "3.8.6" From f031625d5e38dae100045437bd23e6e6d6e9dc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Mon, 9 Jan 2017 14:35:01 -0300 Subject: [PATCH 14/16] Starting next development cycle --- README.markdown | 8 ++++---- project/Build.scala | 2 +- project/plugins.sbt | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.markdown b/README.markdown index 9977b309..75d25168 100644 --- a/README.markdown +++ b/README.markdown @@ -54,7 +54,7 @@ You can view the project's [CHANGELOG here](CHANGELOG.md). And if you're in a hurry, you can include them in your build like this, if you're using PostgreSQL: ```scala -"com.github.mauricio" %% "postgresql-async" % "0.2.20" +"com.github.mauricio" %% "postgresql-async" % "0.2.21" ``` Or Maven: @@ -63,14 +63,14 @@ Or Maven: com.github.mauricio postgresql-async_2.11 - 0.2.20 + 0.2.21 ``` And if you're into MySQL: ```scala -"com.github.mauricio" %% "mysql-async" % "0.2.20" +"com.github.mauricio" %% "mysql-async" % "0.2.21" ``` Or Maven: @@ -79,7 +79,7 @@ Or Maven: com.github.mauricio mysql-async_2.11 - 0.2.20 + 0.2.21 ``` diff --git a/project/Build.scala b/project/Build.scala index f70240ff..b543b050 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -49,7 +49,7 @@ object ProjectBuild extends Build { object Configuration { - val commonVersion = "0.2.21" + val commonVersion = "0.2.22-SNAPSHOT" val projectScalaVersion = "2.12.1" val specs2Version = "3.8.6" diff --git a/project/plugins.sbt b/project/plugins.sbt index d271b7f7..0e9ec632 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.0") resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" + +// pgpSigningKey := Some(0xB98761578C650D77L) From ef3e27b8c34df8b5d4ef638c0113b4c951cb4c98 Mon Sep 17 00:00:00 2001 From: Dominik Dorn Date: Tue, 28 Feb 2017 20:15:31 +0100 Subject: [PATCH 15/16] updated README to mention Scala 2.12 support --- README.markdown | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 75d25168..b15d4885 100644 --- a/README.markdown +++ b/README.markdown @@ -1,7 +1,7 @@ -- [[![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala 2.10 and 2.11](#!build-statushttpstravis-ciorgmauriciopostgresql-asyncpnghttpstravis-ciorgmauriciopostgresql-async-postgresql-async-&-mysql-async---async-netty-based-database-drivers-for-mysql-and-postgresql-written-in-scala-210-and-211) +- [[![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala 2.10, 2.11 and 2.12](#!build-statushttpstravis-ciorgmauriciopostgresql-asyncpnghttpstravis-ciorgmauriciopostgresql-async-postgresql-async-&-mysql-async---async-netty-based-database-drivers-for-mysql-and-postgresql-written-in-scala-210-and-211) - [Abstractions and integrations](#abstractions-and-integrations) - [Include them as dependencies](#include-them-as-dependencies) - [Database connections and encodings](#database-connections-and-encodings) @@ -22,7 +22,7 @@ -# [![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala 2.10 and 2.11 +# [![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala 2.10, 2.11 and 2.12 The main goal for this project is to implement simple, async, performant and reliable database drivers for PostgreSQL and MySQL in Scala. This is not supposed to be a JDBC replacement, these drivers aim to cover the common @@ -67,6 +67,15 @@ Or Maven: ``` +respectively for Scala 2.12: +```xml + + com.github.mauricio + postgresql-async_2.12 + 0.2.21 + +``` + And if you're into MySQL: ```scala @@ -82,6 +91,14 @@ Or Maven: 0.2.21 ``` +respectively for Scala 2.12: +```xml + + com.github.mauricio + mysql-async_2.12 + 0.2.21 + +``` ## Database connections and encodings From 5716ac43818b6be0dc4fcc2b2655dde3411cdbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Linhares?= Date: Tue, 21 Aug 2018 13:52:25 -0400 Subject: [PATCH 16/16] Adding message with project not being maintained anymore --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index b15d4885..79f4b057 100644 --- a/README.markdown +++ b/README.markdown @@ -1,7 +1,7 @@ -- [[![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala 2.10, 2.11 and 2.12](#!build-statushttpstravis-ciorgmauriciopostgresql-asyncpnghttpstravis-ciorgmauriciopostgresql-async-postgresql-async-&-mysql-async---async-netty-based-database-drivers-for-mysql-and-postgresql-written-in-scala-210-and-211) +- This project is not being maintained anymore, feel free to fork and work on it - [Abstractions and integrations](#abstractions-and-integrations) - [Include them as dependencies](#include-them-as-dependencies) - [Database connections and encodings](#database-connections-and-encodings) @@ -22,7 +22,7 @@ -# [![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) postgresql-async & mysql-async - async, Netty based, database drivers for MySQL and PostgreSQL written in Scala 2.10, 2.11 and 2.12 +# [![Build Status](https://travis-ci.org/mauricio/postgresql-async.png)](https://travis-ci.org/mauricio/postgresql-async) This project is not being maintained anymore, feel free to fork and work on it The main goal for this project is to implement simple, async, performant and reliable database drivers for PostgreSQL and MySQL in Scala. This is not supposed to be a JDBC replacement, these drivers aim to cover the common